Add BOF Requests to the datatracker. Commit ready for merge.
- Legacy-Id: 19228
This commit is contained in:
commit
84717102b0
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2010-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2010-2021, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -11,7 +11,8 @@ from .models import (StateType, State, RelatedDocument, DocumentAuthor, Document
|
|||
StateDocEvent, ConsensusDocEvent, BallotType, BallotDocEvent, WriteupDocEvent, LastCallDocEvent,
|
||||
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
|
||||
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
|
||||
ReviewAssignmentDocEvent, IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder )
|
||||
ReviewAssignmentDocEvent, IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
|
||||
BofreqEditorDocEvent, BofreqResponsibleDocEvent )
|
||||
|
||||
from ietf.utils.validators import validate_external_resource_value
|
||||
|
||||
|
@ -192,6 +193,14 @@ admin.site.register(BallotPositionDocEvent, BallotPositionDocEventAdmin)
|
|||
class IRSGBallotDocEventAdmin(DocEventAdmin):
|
||||
raw_id_fields = ["doc", "by"]
|
||||
admin.site.register(IRSGBallotDocEvent, IRSGBallotDocEventAdmin)
|
||||
|
||||
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', ]
|
||||
|
|
|
@ -13,8 +13,12 @@ from django.conf import settings
|
|||
|
||||
from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
|
||||
StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent,
|
||||
DocumentActionHolder)
|
||||
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
|
||||
|
||||
|
||||
def draft_name_generator(type_id,group,n):
|
||||
return '%s-%s-%s-%s%d'%(
|
||||
|
@ -379,3 +383,59 @@ class DocumentAuthorFactory(factory.DjangoModelFactory):
|
|||
|
||||
class WgDocumentAuthorFactory(DocumentAuthorFactory):
|
||||
document = factory.SubFactory(WgDraftFactory)
|
||||
|
||||
class BofreqEditorDocEventFactory(DocEventFactory):
|
||||
class Meta:
|
||||
model = BofreqEditorDocEvent
|
||||
|
||||
type = "changed_editors"
|
||||
doc = factory.SubFactory('ietf.doc.factories.BofreqFactory')
|
||||
|
||||
|
||||
@factory.post_generation
|
||||
def editors(obj, create, extracted, **kwargs):
|
||||
if not create:
|
||||
return
|
||||
if extracted:
|
||||
obj.editors.set(extracted)
|
||||
else:
|
||||
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):
|
||||
if not create:
|
||||
return
|
||||
if extracted:
|
||||
for (state_type_id,state_slug) in extracted:
|
||||
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
|
||||
else:
|
||||
obj.set_state(State.objects.get(type_id='bofreq',slug='proposed'))
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ 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
|
||||
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
|
||||
|
@ -689,3 +690,43 @@ def send_external_resource_change_request(request, doc, submitter_info, requeste
|
|||
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
|
||||
),
|
||||
cc=list(cc),)
|
||||
|
||||
def email_bofreq_title_changed(request, bofreq):
|
||||
addrs = gather_address_lists('bofreq_title_changed', doc=bofreq)
|
||||
|
||||
send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
|
||||
f'BOF Request title changed : {bofreq.name}',
|
||||
'doc/mail/bofreq_title_changed.txt',
|
||||
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_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=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):
|
||||
addrs = gather_address_lists('bofreq_new_revision', doc=bofreq)
|
||||
send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
|
||||
f'New BOF request revision uploaded: {bofreq.name}-{bofreq.rev}',
|
||||
'doc/mail/bofreq_new_revision.txt',
|
||||
dict(bofreq=bofreq, request=request ),
|
||||
cc=addrs.cc)
|
||||
|
|
36
ietf/doc/migrations/0042_bofreq_states.py
Normal file
36
ietf/doc/migrations/0042_bofreq_states.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Copyright The IETF Trust 2021 All Rights Reserved
|
||||
|
||||
# Generated by Django 2.2.23 on 2021-05-21 13:29
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
StateType = apps.get_model('doc', 'StateType')
|
||||
State = apps.get_model('doc', 'State')
|
||||
|
||||
StateType.objects.create(slug='bofreq', label='BOF Request State')
|
||||
proposed = State.objects.create(type_id='bofreq', slug='proposed', name='Proposed', used=True, desc='The BOF request is proposed', order=0)
|
||||
approved = State.objects.create(type_id='bofreq', slug='approved', name='Approved', used=True, desc='The BOF request is approved', order=1)
|
||||
declined = State.objects.create(type_id='bofreq', slug='declined', name='Declined', used=True, desc='The BOF request is declined', order=2)
|
||||
replaced = State.objects.create(type_id='bofreq', slug='replaced', name='Replaced', used=True, desc='The BOF request is proposed', order=3)
|
||||
abandoned = State.objects.create(type_id='bofreq', slug='abandoned', name='Abandoned', used=True, desc='The BOF request is abandoned', order=4)
|
||||
|
||||
proposed.next_states.set([approved,declined,replaced,abandoned])
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
StateType = apps.get_model('doc', 'StateType')
|
||||
State = apps.get_model('doc', 'State')
|
||||
State.objects.filter(type_id='bofreq').delete()
|
||||
StateType.objects.filter(slug='bofreq').delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('doc', '0041_add_documentactionholder'),
|
||||
('name', '0027_add_bofrequest'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
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',),
|
||||
),
|
||||
]
|
|
@ -142,6 +142,8 @@ class DocumentInfo(models.Model):
|
|||
self._cached_file_path = settings.CONFLICT_REVIEW_PATH
|
||||
elif self.type_id == "statchg":
|
||||
self._cached_file_path = settings.STATUS_CHANGE_PATH
|
||||
elif self.type_id == "bofreq":
|
||||
self._cached_file_path = settings.BOFREQ_PATH
|
||||
else:
|
||||
self._cached_file_path = settings.DOCUMENT_PATH_PATTERN.format(doc=self)
|
||||
return self._cached_file_path
|
||||
|
@ -163,6 +165,8 @@ class DocumentInfo(models.Model):
|
|||
elif self.type_id == 'review':
|
||||
# TODO: This will be wrong if a review is updated on the same day it was created (or updated more than once on the same day)
|
||||
self._cached_base_name = "%s.txt" % self.name
|
||||
elif self.type_id == 'bofreq':
|
||||
self._cached_base_name = "%s-%s.md" % (self.name, self.rev)
|
||||
else:
|
||||
if self.rev:
|
||||
self._cached_base_name = "%s-%s.txt" % (self.canonical_name(), self.rev)
|
||||
|
@ -1145,6 +1149,9 @@ EVENT_TYPES = [
|
|||
# IPR events
|
||||
("posted_related_ipr", "Posted related IPR"),
|
||||
("removed_related_ipr", "Removed related IPR"),
|
||||
|
||||
# Bofreq Editor events
|
||||
("changed_editors", "Changed BOF Request editors")
|
||||
]
|
||||
|
||||
class DocEvent(models.Model):
|
||||
|
@ -1340,3 +1347,11 @@ class EditedAuthorsDocEvent(DocEvent):
|
|||
Example 'basis' values might be from ['manually adjusted','recomputed by parsing document', etc.]
|
||||
"""
|
||||
basis = models.CharField(help_text="What is the source or reasoning for the changes to the author list",max_length=255)
|
||||
|
||||
class BofreqEditorDocEvent(DocEvent):
|
||||
""" 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,
|
||||
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
|
||||
ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL,
|
||||
IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder )
|
||||
IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
|
||||
BofreqEditorDocEvent,BofreqResponsibleDocEvent)
|
||||
|
||||
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
|
||||
class BallotTypeResource(ModelResource):
|
||||
|
@ -806,3 +807,55 @@ class DocumentActionHolderResource(ModelResource):
|
|||
"person": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.doc.register(DocumentActionHolderResource())
|
||||
|
||||
|
||||
from ietf.person.resources import PersonResource
|
||||
class BofreqEditorDocEventResource(ModelResource):
|
||||
by = ToOneField(PersonResource, 'by')
|
||||
doc = ToOneField(DocumentResource, 'doc')
|
||||
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
|
||||
editors = ToManyField(PersonResource, 'editors', null=True)
|
||||
class Meta:
|
||||
queryset = BofreqEditorDocEvent.objects.all()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'bofreqeditordocevent'
|
||||
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,
|
||||
"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())
|
||||
|
|
393
ietf/doc/tests_bofreq.py
Normal file
393
ietf/doc/tests_bofreq.py
Normal file
|
@ -0,0 +1,393 @@
|
|||
# Copyright The IETF Trust 2021 All Rights Reserved
|
||||
|
||||
import datetime
|
||||
import debug # pyflakes:ignore
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from pyquery import PyQuery
|
||||
from random import randint
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory
|
||||
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):
|
||||
self.bofreq_dir = self.tempdir('bofreq')
|
||||
self.saved_bofreq_path = settings.BOFREQ_PATH
|
||||
settings.BOFREQ_PATH = self.bofreq_dir
|
||||
|
||||
def tearDown(self):
|
||||
settings.BOFREQ_PATH = self.saved_bofreq_path
|
||||
shutil.rmtree(self.bofreq_dir)
|
||||
|
||||
def write_bofreq_file(self, bofreq):
|
||||
fname = os.path.join(self.bofreq_dir, "%s-%s.md" % (bofreq.canonical_name(), bofreq.rev))
|
||||
with io.open(fname, "w") as f:
|
||||
f.write(f"""# This is a test bofreq.
|
||||
Version: {bofreq.rev}
|
||||
|
||||
## A section
|
||||
|
||||
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)
|
||||
for i in range(3*len(states)):
|
||||
BofreqFactory(states=[('bofreq',states[i%len(states)].slug)],newrevisiondocevent__time=datetime.datetime.today()-datetime.timedelta(days=randint(0,20)))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
for state in states:
|
||||
self.assertEqual(len(q(f'#bofreqs-{state.slug}')), 1)
|
||||
self.assertEqual(len(q(f'#bofreqs-{state.slug} tbody tr')), 3)
|
||||
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):
|
||||
doc = BofreqFactory()
|
||||
doc.save_with_history(doc.docevent_set.all())
|
||||
self.write_bofreq_file(doc)
|
||||
nr_event = NewRevisionDocEventFactory(doc=doc,rev='01')
|
||||
doc.rev='01'
|
||||
doc.save_with_history([nr_event])
|
||||
self.write_bofreq_file(doc)
|
||||
editors = bofreq_editors(doc)
|
||||
responsible = bofreq_responsible(doc)
|
||||
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=doc.name))
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r,'Version: 01',status_code=200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(0, len(q('td.edit>a.btn')))
|
||||
self.assertEqual([],q('#change-request'))
|
||||
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(6, len(q('td.edit>a.btn')))
|
||||
self.client.logout()
|
||||
self.assertNotEqual([],q('#change-request'))
|
||||
editor = editors.first().user.username
|
||||
self.client.login(username=editor, password=editor+"+password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(3, len(q('td.edit>a.btn')))
|
||||
self.assertNotEqual([],q('#change-request'))
|
||||
self.client.logout()
|
||||
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=doc,rev='00'))
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r,'Version: 00',status_code=200)
|
||||
self.assertContains(r,'is for an older version')
|
||||
|
||||
|
||||
def test_edit_title(self):
|
||||
doc = BofreqFactory()
|
||||
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'))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual(title, doc.title)
|
||||
nobody = PersonFactory()
|
||||
self.client.login(username=nobody.user.username,password=nobody.user.username+'+password')
|
||||
r = self.client.post(url,dict(title='New title'))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual(title, doc.title)
|
||||
self.client.logout()
|
||||
for username in ('secretary', 'ad', 'iab-member', editor.user.username):
|
||||
self.client.login(username=username, password=username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
docevent_count = doc.docevent_set.count()
|
||||
empty_outbox()
|
||||
r = self.client.post(url,dict(title=username))
|
||||
self.assertEqual(r.status_code,302)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual(doc.title, username)
|
||||
self.assertEqual(docevent_count+1, doc.docevent_set.count())
|
||||
self.assertEqual(1, len(outbox))
|
||||
self.client.logout()
|
||||
|
||||
def state_pk_as_str(self, type_id, slug):
|
||||
return str(State.objects.get(type_id=type_id, slug=slug).pk)
|
||||
|
||||
def test_edit_state(self):
|
||||
doc = BofreqFactory()
|
||||
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')))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual(state, doc.get_state('bofreq'))
|
||||
self.client.login(username=editor.user.username,password=editor.user.username+'+password')
|
||||
r = self.client.post(url, dict(new_state=self.state_pk_as_str('bofreq','approved')))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual(state,doc.get_state('bofreq'))
|
||||
self.client.logout()
|
||||
for username in ('secretary', 'ad', 'iab-member'):
|
||||
doc.set_state(state)
|
||||
self.client.login(username=username,password=username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
docevent_count = doc.docevent_set.count()
|
||||
r = self.client.post(url,dict(new_state=self.state_pk_as_str('bofreq','approved' if username=='secretary' else 'declined'),comment=f'{username}-2309hnf'))
|
||||
self.assertEqual(r.status_code,302)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual('approved' if username=='secretary' else 'declined',doc.get_state_slug('bofreq'))
|
||||
self.assertEqual(docevent_count+2, doc.docevent_set.count())
|
||||
self.assertIn(f'{username}-2309hnf',doc.latest_event(type='added_comment').desc)
|
||||
self.client.logout()
|
||||
|
||||
def test_change_editors(self):
|
||||
doc = BofreqFactory()
|
||||
previous_editors = list(bofreq_editors(doc))
|
||||
acting_editor = previous_editors[0]
|
||||
new_editors = set(previous_editors)
|
||||
new_editors.discard(acting_editor)
|
||||
new_editors.add(PersonFactory())
|
||||
url = urlreverse('ietf.doc.views_bofreq.change_editors', kwargs=dict(name=doc.name))
|
||||
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 = 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 = 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'):
|
||||
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 editor in previous_editors:
|
||||
self.assertIn(editor.name,unescaped)
|
||||
new_editors = set(previous_editors)
|
||||
new_editors.discard(acting_editor)
|
||||
new_editors.add(PersonFactory())
|
||||
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 = 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))
|
||||
|
||||
rev = doc.rev
|
||||
r = self.client.post(url,{'bofreq_submission':'enter','bofreq_content':'# oiwefrase'})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual(rev, doc.rev)
|
||||
|
||||
nobody = PersonFactory()
|
||||
self.client.login(username=nobody.user.username, password=nobody.user.username+'+password')
|
||||
r = self.client.post(url,{'bofreq_submission':'enter','bofreq_content':'# oiwefrase'})
|
||||
self.assertEqual(r.status_code, 403)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual(rev, doc.rev)
|
||||
self.client.logout()
|
||||
|
||||
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)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
file = NamedTemporaryFile(delete=False,mode="w+",encoding='utf-8')
|
||||
file.write(f'# {username}')
|
||||
file.close()
|
||||
for postdict in [
|
||||
{'bofreq_submission':'enter','bofreq_content':f'# {username}'},
|
||||
{'bofreq_submission':'upload','bofreq_file':open(file.name,'rb')},
|
||||
]:
|
||||
docevent_count = doc.docevent_set.count()
|
||||
empty_outbox()
|
||||
r = self.client.post(url, postdict)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = reload_db_objects(doc)
|
||||
self.assertEqual('%02d'%(int(rev)+1) ,doc.rev)
|
||||
self.assertEqual(f'# {username}', doc.text())
|
||||
self.assertEqual(docevent_count+1, doc.docevent_set.count())
|
||||
self.assertEqual(1, len(outbox))
|
||||
rev = doc.rev
|
||||
self.client.logout()
|
||||
os.unlink(file.name)
|
||||
|
||||
def test_start_new_bofreq(self):
|
||||
url = urlreverse('ietf.doc.views_bofreq.new_bof_request')
|
||||
nobody = PersonFactory()
|
||||
login_testing_unauthorized(self,nobody.user.username,url)
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r,'Fill in the details below. Keep items in the order they appear here.',status_code=200)
|
||||
r = self.client.post(url, dict(title='default',
|
||||
bofreq_submission='enter',
|
||||
bofreq_content=render_to_string('doc/bofreq/bofreq_template.md',{})))
|
||||
self.assertContains(r, 'The example content may not be saved.', status_code=200)
|
||||
file = NamedTemporaryFile(delete=False,mode="w+",encoding='utf-8')
|
||||
file.write('some stuff')
|
||||
file.close()
|
||||
for postdict in [
|
||||
dict(title='title one', bofreq_submission='enter', bofreq_content='some stuff'),
|
||||
dict(title='title two', bofreq_submission='upload', bofreq_file=open(file.name,'rb')),
|
||||
]:
|
||||
empty_outbox()
|
||||
r = self.client.post(url, postdict)
|
||||
self.assertEqual(r.status_code,302)
|
||||
name = f"bofreq-{postdict['title']}".replace(' ','-')
|
||||
bofreq = Document.objects.filter(name=name,type_id='bofreq').first()
|
||||
self.assertIsNotNone(bofreq)
|
||||
self.assertIsNotNone(DocAlias.objects.filter(name=name).first())
|
||||
self.assertEqual(bofreq.title, postdict['title'])
|
||||
self.assertEqual(bofreq.rev, '00')
|
||||
self.assertEqual(bofreq.get_state_slug(), 'proposed')
|
||||
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)
|
||||
os.unlink(file.name)
|
||||
existing_bofreq = BofreqFactory()
|
||||
for postdict in [
|
||||
dict(title='', bofreq_submission='enter', bofreq_content='some stuff'),
|
||||
dict(title='a title', bofreq_submission='enter', bofreq_content=''),
|
||||
dict(title=existing_bofreq.title, bofreq_submission='enter', bofreq_content='some stuff'),
|
||||
dict(title='森川', bofreq_submission='enter', bofreq_content='some stuff'),
|
||||
dict(title='a title', bofreq_submission='', bofreq_content='some stuff'),
|
||||
]:
|
||||
r = self.client.post(url,postdict)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(q('form div.has-error'))
|
||||
|
||||
def test_post_proposed_restrictions(self):
|
||||
states = State.objects.filter(type_id='bofreq').exclude(slug='proposed')
|
||||
bofreq = BofreqFactory()
|
||||
editor = bofreq_editors(bofreq).first()
|
||||
|
||||
for view in ('submit', 'change_editors', 'edit_title'):
|
||||
url = urlreverse(f'ietf.doc.views_bofreq.{view}', kwargs=dict(name=bofreq.name))
|
||||
for state in states:
|
||||
bofreq.set_state(state)
|
||||
for username in ('secretary', 'ad', 'iab-member'):
|
||||
self.client.login(username=username, password=username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.client.logout()
|
||||
self.client.login(username=editor.user.username, password=editor.user.username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 403, f'editor should not be able to use {view} in state {state.slug}')
|
||||
self.client.logout()
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=bofreq.name))
|
||||
self.client.login(username=editor.user.username, password=editor.user.username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(0, len(q('td.edit>a.btn')))
|
||||
self.assertEqual([],q('#change-request'))
|
||||
|
|
@ -275,7 +275,7 @@ class EditCharterTests(TestCase):
|
|||
login_testing_unauthorized(self, "secretary", url)
|
||||
response=self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
response = self.client.post(url,{'comment':'Testing Abandoning a Bof Charter'})
|
||||
response = self.client.post(url,{'comment':'Testing Abandoning a BOF Charter'})
|
||||
self.assertEqual(response.status_code,302)
|
||||
charter = Document.objects.get(pk=charter.pk)
|
||||
self.assertEqual(charter.group.state_id,'abandon')
|
||||
|
|
|
@ -37,7 +37,7 @@ from django.conf.urls import include
|
|||
from django.views.generic import RedirectView
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.doc import views_search, views_draft, views_ballot, views_status_change, views_doc, views_downref, views_stats, views_help
|
||||
from ietf.doc import views_search, views_draft, views_ballot, views_status_change, views_doc, views_downref, views_stats, views_help, views_bofreq
|
||||
from ietf.utils.urls import url
|
||||
|
||||
session_patterns = [
|
||||
|
@ -54,6 +54,8 @@ urlpatterns = [
|
|||
url(r'^ad2/(?P<name>[\w.-]+)/$', RedirectView.as_view(url='/doc/ad/%(name)s/', permanent=True)),
|
||||
url(r'^rfc-status-changes/?$', views_status_change.rfc_status_changes),
|
||||
url(r'^start-rfc-status-change/(?:%(name)s/)?$' % settings.URL_REGEXPS, views_status_change.start_rfc_status_change),
|
||||
url(r'^bof-requests/?$', views_bofreq.bof_requests),
|
||||
url(r'^bof-requests/new/$', views_bofreq.new_bof_request),
|
||||
url(r'^iesg/?$', views_search.drafts_in_iesg_process),
|
||||
url(r'^email-aliases/?$', views_doc.email_aliases),
|
||||
url(r'^downref/?$', views_downref.downref_registry),
|
||||
|
@ -145,6 +147,7 @@ urlpatterns = [
|
|||
url(r'^%(name)s/meetings/?$' % settings.URL_REGEXPS, views_doc.all_presentations),
|
||||
|
||||
url(r'^%(charter)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_charter')),
|
||||
url(r'^%(bofreq)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_bofreq')),
|
||||
url(r'^%(name)s/conflict-review/' % settings.URL_REGEXPS, include('ietf.doc.urls_conflict_review')),
|
||||
url(r'^%(name)s/status-change/' % settings.URL_REGEXPS, include('ietf.doc.urls_status_change')),
|
||||
url(r'^%(name)s/material/' % settings.URL_REGEXPS, include('ietf.doc.urls_material')),
|
||||
|
|
11
ietf/doc/urls_bofreq.py
Normal file
11
ietf/doc/urls_bofreq.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
from ietf.doc import views_bofreq
|
||||
from ietf.utils.urls import url
|
||||
|
||||
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'^responsible/$', views_bofreq.change_responsible),
|
||||
]
|
|
@ -29,7 +29,7 @@ from ietf.doc.models import DocEvent, ConsensusDocEvent, BallotDocEvent, IRSGBal
|
|||
from ietf.doc.models import TelechatDocEvent, DocumentActionHolder, EditedAuthorsDocEvent
|
||||
from ietf.name.models import DocReminderTypeName, DocRelationshipName
|
||||
from ietf.group.models import Role, Group
|
||||
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, is_individual_draft_author
|
||||
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, is_individual_draft_author, is_bofreq_editor
|
||||
from ietf.person.models import Person
|
||||
from ietf.review.models import ReviewWish
|
||||
from ietf.utils import draft, text
|
||||
|
@ -152,7 +152,8 @@ def can_unadopt_draft(user, doc):
|
|||
def can_edit_docextresources(user, doc):
|
||||
return (has_role(user, ("Secretariat", "Area Director"))
|
||||
or is_authorized_in_doc_stream(user, doc)
|
||||
or is_individual_draft_author(user, doc))
|
||||
or is_individual_draft_author(user, doc)
|
||||
or is_bofreq_editor(user, doc))
|
||||
|
||||
def two_thirds_rule( recused=0 ):
|
||||
# For standards-track, need positions from 2/3 of the non-recused current IESG.
|
||||
|
@ -1219,13 +1220,15 @@ def update_doc_extresources(doc, new_resources, by):
|
|||
if old_res_strs == new_res_strs:
|
||||
return False # no change
|
||||
|
||||
old_res_strs = f'\n\n{old_res_strs}\n\n' if old_res_strs else ' None '
|
||||
new_res_strs = f'\n\n{new_res_strs}' if new_res_strs else ' None'
|
||||
|
||||
doc.docextresource_set.all().delete()
|
||||
for new_res in new_resources:
|
||||
new_res.doc = doc
|
||||
new_res.save()
|
||||
e = DocEvent(doc=doc, rev=doc.rev, by=by, type='changed_document')
|
||||
e.desc = "Changed document external resources from:\n\n%s\n\nto:\n\n%s" % (
|
||||
old_res_strs, new_res_strs)
|
||||
e.desc = f"Changed document external resources from:{old_res_strs}to:{new_res_strs}"
|
||||
e.save()
|
||||
doc.save_with_history([e])
|
||||
return True
|
||||
|
|
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()
|
348
ietf/doc/views_bofreq.py
Normal file
348
ietf/doc/views_bofreq.py
Normal file
|
@ -0,0 +1,348 @@
|
|||
# Copyright The IETF Trust 2021 All Rights Reserved
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
import io
|
||||
import markdown
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
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, 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
|
||||
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)
|
||||
req.responsible = bofreq_responsible(req)
|
||||
req.editors = bofreq_editors(req)
|
||||
sorted_reqs = sorted(sorted(reqs, key=lambda doc: doc.latest_revision_event.time, reverse=True), key=lambda doc: doc.get_state().order)
|
||||
return render(request, 'doc/bofreq/bof_requests.html',dict(reqs=sorted_reqs))
|
||||
|
||||
|
||||
class BofreqUploadForm(forms.Form):
|
||||
ACTIONS = [
|
||||
("enter", "Enter content directly"),
|
||||
("upload", "Upload content from file"),
|
||||
]
|
||||
bofreq_submission = forms.ChoiceField(choices=ACTIONS, widget=forms.RadioSelect)
|
||||
bofreq_file = forms.FileField(label="Markdown source file to upload", required=False)
|
||||
bofreq_content = forms.CharField(widget=forms.Textarea(attrs={'rows':30}), required=False, strip=False)
|
||||
|
||||
def clean_bofreq_content(self):
|
||||
content = self.cleaned_data["bofreq_content"].replace("\r", "")
|
||||
default_content = render_to_string('doc/bofreq/bofreq_template.md',{})
|
||||
if content==default_content:
|
||||
raise forms.ValidationError('The example content may not be saved. Edit it as instructed to document this BOF request.')
|
||||
try:
|
||||
_ = markdown.markdown(content, extensions=['extra'])
|
||||
except Exception as e:
|
||||
raise forms.ValidationError(f'Markdown processing failed: {e}')
|
||||
return content
|
||||
|
||||
def clean_bofreq_file(self):
|
||||
content = get_cleaned_text_file_content(self.cleaned_data["bofreq_file"])
|
||||
try:
|
||||
_ = markdown.markdown(content, extensions=['extra'])
|
||||
except Exception as e:
|
||||
raise forms.ValidationError(f'Markdown processing failed: {e}')
|
||||
return content
|
||||
|
||||
def clean(self):
|
||||
def require_field(f):
|
||||
if not self.cleaned_data.get(f):
|
||||
self.add_error(f, forms.ValidationError("You must fill in this field."))
|
||||
|
||||
submission_method = self.cleaned_data.get("bofreq_submission")
|
||||
if submission_method == "enter":
|
||||
require_field("bofreq_content")
|
||||
elif submission_method == "upload":
|
||||
require_field("bofreq_file")
|
||||
|
||||
|
||||
@login_required
|
||||
def submit(request, name):
|
||||
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||
previous_editors = bofreq_editors(bofreq)
|
||||
state_id = bofreq.get_state_slug('bofreq')
|
||||
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or (state_id=='proposed' and request.user.person in previous_editors)):
|
||||
permission_denied(request,"You do not have permission to upload a new revision of this BOF Request")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = BofreqUploadForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
bofreq.rev = "%02d" % (int(bofreq.rev)+1)
|
||||
e = NewRevisionDocEvent.objects.create(
|
||||
type="new_revision",
|
||||
doc=bofreq,
|
||||
by=request.user.person,
|
||||
rev=bofreq.rev,
|
||||
desc='New revision available',
|
||||
time=bofreq.time,
|
||||
)
|
||||
bofreq.save_with_history([e])
|
||||
bofreq_submission = form.cleaned_data['bofreq_submission']
|
||||
if bofreq_submission == "upload":
|
||||
content = form.cleaned_data['bofreq_file']
|
||||
else:
|
||||
content = form.cleaned_data['bofreq_content']
|
||||
with io.open(bofreq.get_file_name(), 'w', encoding='utf-8') as destination:
|
||||
destination.write(content)
|
||||
email_bofreq_new_revision(request, bofreq)
|
||||
return redirect('ietf.doc.views_doc.document_main', name=bofreq.name)
|
||||
|
||||
else:
|
||||
init = {'bofreq_content':bofreq.text_or_error(),
|
||||
'bofreq_submission':'enter',
|
||||
}
|
||||
form = BofreqUploadForm(initial=init)
|
||||
return render(request, 'doc/bofreq/upload_content.html',
|
||||
{'form':form,'doc':bofreq})
|
||||
|
||||
class NewBofreqForm(BofreqUploadForm):
|
||||
title = forms.CharField(max_length=255)
|
||||
field_order = ['title','bofreq_submission','bofreq_file','bofreq_content']
|
||||
|
||||
def name_from_title(self,title):
|
||||
name = 'bofreq-' + xslugify(title).replace('_', '-')[:128]
|
||||
return name
|
||||
|
||||
def clean_title(self):
|
||||
title = self.cleaned_data['title']
|
||||
name = self.name_from_title(title)
|
||||
if name == 'bofreq-':
|
||||
raise forms.ValidationError('The filename derived from this title is empty. Please include a few descriptive words using ascii or numeric characters')
|
||||
if Document.objects.filter(name=name).exists():
|
||||
raise forms.ValidationError('This title produces a filename already used by an existing BOF request')
|
||||
return title
|
||||
|
||||
@login_required
|
||||
def new_bof_request(request):
|
||||
|
||||
if request.method == 'POST':
|
||||
form = NewBofreqForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
title = form.cleaned_data['title']
|
||||
name = form.name_from_title(title)
|
||||
bofreq = Document.objects.create(
|
||||
type_id='bofreq',
|
||||
name = name,
|
||||
title = title,
|
||||
abstract = '',
|
||||
rev = '00',
|
||||
)
|
||||
bofreq.set_state(State.objects.get(type_id='bofreq',slug='proposed'))
|
||||
e1 = NewRevisionDocEvent.objects.create(
|
||||
type="new_revision",
|
||||
doc=bofreq,
|
||||
by=request.user.person,
|
||||
rev=bofreq.rev,
|
||||
desc='New revision available',
|
||||
time=bofreq.time,
|
||||
)
|
||||
e2 = BofreqEditorDocEvent.objects.create(
|
||||
type="changed_editors",
|
||||
doc=bofreq,
|
||||
rev=bofreq.rev,
|
||||
by=request.user.person,
|
||||
desc= f'Editors changed to {request.user.person.name}',
|
||||
)
|
||||
e2.editors.set([request.user.person])
|
||||
bofreq.save_with_history([e1,e2])
|
||||
alias = DocAlias.objects.create(name=name)
|
||||
alias.docs.set([bofreq])
|
||||
bofreq_submission = form.cleaned_data['bofreq_submission']
|
||||
if bofreq_submission == "upload":
|
||||
content = form.cleaned_data['bofreq_file']
|
||||
else:
|
||||
content = form.cleaned_data['bofreq_content']
|
||||
with io.open(bofreq.get_file_name(), 'w', encoding='utf-8') as destination:
|
||||
destination.write(content)
|
||||
email_bofreq_new_revision(request, bofreq)
|
||||
return redirect('ietf.doc.views_doc.document_main', name=bofreq.name)
|
||||
|
||||
else:
|
||||
init = {'bofreq_content':render_to_string('doc/bofreq/bofreq_template.md',{}),
|
||||
'bofreq_submission':'enter',
|
||||
}
|
||||
form = NewBofreqForm(initial=init)
|
||||
return render(request, 'doc/bofreq/new_bofreq.html',
|
||||
{'form':form})
|
||||
|
||||
|
||||
class ChangeEditorsForm(forms.Form):
|
||||
editors = SearchablePersonsField(required=False)
|
||||
|
||||
|
||||
@login_required
|
||||
def change_editors(request, name):
|
||||
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||
previous_editors = bofreq_editors(bofreq)
|
||||
state_id = bofreq.get_state_slug('bofreq')
|
||||
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or (state_id=='proposed' and request.user.person in previous_editors)):
|
||||
permission_denied(request,"You do not have permission to change this document's editors")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ChangeEditorsForm(request.POST)
|
||||
if form.is_valid():
|
||||
new_editors = form.cleaned_data['editors']
|
||||
if set(new_editors) != set(previous_editors):
|
||||
e = BofreqEditorDocEvent(type="changed_editors", doc=bofreq, rev=bofreq.rev, by=request.user.person)
|
||||
e.desc = f'Editors changed to {", ".join([p.name for p in new_editors])}'
|
||||
e.save()
|
||||
e.editors.set(new_editors)
|
||||
bofreq.save_with_history([e])
|
||||
email_bofreq_editors_changed(request, bofreq, previous_editors)
|
||||
return redirect("ietf.doc.views_doc.document_main", name=bofreq.name)
|
||||
else:
|
||||
init = { "editors" : previous_editors }
|
||||
form = ChangeEditorsForm(initial=init)
|
||||
titletext = bofreq.get_base_name()
|
||||
return render(request, 'doc/bofreq/change_editors.html',
|
||||
{'form': form,
|
||||
'doc': bofreq,
|
||||
'titletext' : titletext,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
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_editors(bofreq)
|
||||
state_id = bofreq.get_state_slug('bofreq')
|
||||
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or (state_id=='proposed' and request.user.person in editors)):
|
||||
permission_denied(request, "You do not have permission to edit this document's title")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ChangeTitleForm(request.POST)
|
||||
if form.is_valid():
|
||||
|
||||
bofreq.title = form.cleaned_data['title']
|
||||
|
||||
c = DocEvent(type="added_comment", doc=bofreq, rev=bofreq.rev, by=request.user.person)
|
||||
c.desc = "Title changed to '%s'"%bofreq.title
|
||||
c.save()
|
||||
|
||||
bofreq.save_with_history([c])
|
||||
email_bofreq_title_changed(request, bofreq)
|
||||
|
||||
return redirect("ietf.doc.views_doc.document_main", name=bofreq.name)
|
||||
|
||||
else:
|
||||
init = { "title" : bofreq.title }
|
||||
form = ChangeTitleForm(initial=init)
|
||||
|
||||
titletext = bofreq.get_base_name()
|
||||
return render(request, 'doc/change_title.html',
|
||||
{'form': form,
|
||||
'doc': bofreq,
|
||||
'titletext' : titletext,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class ChangeStateForm(forms.Form):
|
||||
new_state = forms.ModelChoiceField(State.objects.filter(type="bofreq", used=True), label="BOF Request State", empty_label=None, required=True)
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the state change history entry.", required=False, strip=False)
|
||||
|
||||
|
||||
@role_required('Area Director', 'Secretariat', 'IAB')
|
||||
def change_state(request, name, option=None):
|
||||
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||
|
||||
login = request.user.person
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ChangeStateForm(request.POST)
|
||||
if form.is_valid():
|
||||
clean = form.cleaned_data
|
||||
new_state = clean['new_state']
|
||||
comment = clean['comment'].rstrip()
|
||||
|
||||
if comment:
|
||||
c = DocEvent(type="added_comment", doc=bofreq, rev=bofreq.rev, by=login)
|
||||
c.desc = comment
|
||||
c.save()
|
||||
|
||||
prev_state = bofreq.get_state()
|
||||
if new_state != prev_state:
|
||||
bofreq.set_state(new_state)
|
||||
events = []
|
||||
events.append(add_state_change_event(bofreq, login, prev_state, new_state))
|
||||
bofreq.save_with_history(events)
|
||||
|
||||
return redirect('ietf.doc.views_doc.document_main', name=bofreq.name)
|
||||
else:
|
||||
s = bofreq.get_state()
|
||||
init = dict(new_state=s.pk if s else None)
|
||||
form = ChangeStateForm(initial=init)
|
||||
|
||||
return render(request, 'doc/change_state.html',
|
||||
dict(form=form,
|
||||
doc=bofreq,
|
||||
login=login,
|
||||
help_url=urlreverse('ietf.doc.views_help.state_help', kwargs=dict(type="bofreq")),
|
||||
))
|
|
@ -40,6 +40,7 @@ import io
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import markdown
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
|
@ -54,14 +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 )
|
||||
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,
|
||||
|
@ -526,6 +528,26 @@ def document_main(request, name, rev=None):
|
|||
can_manage=can_manage,
|
||||
))
|
||||
|
||||
if doc.type_id == "bofreq":
|
||||
content = markdown.markdown(doc.text_or_error(),extensions=['extra'])
|
||||
editors = bofreq_editors(doc)
|
||||
responsible = bofreq_responsible(doc)
|
||||
can_manage = has_role(request.user,['Secretariat', 'Area Director', 'IAB'])
|
||||
editor_can_manage = doc.get_state_slug('bofreq')=='proposed' and request.user.is_authenticated and request.user.person in editors
|
||||
|
||||
return render(request, "doc/document_bofreq.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
revisions=revisions,
|
||||
latest_rev=latest_rev,
|
||||
content=content,
|
||||
snapshot=snapshot,
|
||||
can_manage=can_manage,
|
||||
editors=editors,
|
||||
responsible=responsible,
|
||||
editor_can_manage=editor_can_manage,
|
||||
))
|
||||
|
||||
if doc.type_id == "conflrev":
|
||||
filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev)
|
||||
pathname = os.path.join(settings.CONFLICT_REVIEW_PATH,filename)
|
||||
|
|
|
@ -17,6 +17,7 @@ def state_help(request, type):
|
|||
"charter": ("charter", "Charter States"),
|
||||
"conflict-review": ("conflrev", "Conflict Review States"),
|
||||
"status-change": ("statchg", "RFC Status Change States"),
|
||||
"bofreq": ("bofreq", "BOF Request States"),
|
||||
}.get(type, (None, None))
|
||||
state_type = get_object_or_404(StateType, slug=slug)
|
||||
|
||||
|
|
|
@ -188,11 +188,11 @@ class GroupForm(forms.Form):
|
|||
|
||||
if existing and existing.type_id == self.group_type:
|
||||
if existing.state_id == "bof":
|
||||
#insert_confirm_field(label="Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name), initial=True)
|
||||
#insert_confirm_field(label="Turn BOF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name), initial=True)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.acronym)
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing BOF (%s)." % existing.acronym)
|
||||
else:
|
||||
#insert_confirm_field(label="Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name), initial=False)
|
||||
if confirmed:
|
||||
|
@ -265,7 +265,7 @@ class GroupForm(forms.Form):
|
|||
state = cleaned_data.get('state', None)
|
||||
parent = cleaned_data.get('parent', None)
|
||||
if state and (state.slug in ['bof', ] and 'parent' in self.fields and not parent):
|
||||
raise forms.ValidationError("You requested the creation of a BoF, but specified no parent area. A parent is required when creating a bof.")
|
||||
raise forms.ValidationError("You requested the creation of a BOF, but specified no parent area. A parent is required when creating a bof.")
|
||||
return cleaned_data
|
||||
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -563,7 +561,7 @@ class GroupEditTests(TestCase):
|
|||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# try elevating BoF to WG
|
||||
# try elevating BOF to WG
|
||||
group.state_id = "bof"
|
||||
group.save()
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.group.models import Role, GroupFeatures
|
||||
from ietf.person.models import Person
|
||||
from ietf.doc.utils_bofreq import bofreq_editors
|
||||
|
||||
def user_is_person(user, person):
|
||||
"""Test whether user is associated with person."""
|
||||
|
@ -194,6 +195,9 @@ def is_individual_draft_author(user, doc):
|
|||
if not user.is_authenticated:
|
||||
return False
|
||||
|
||||
if not doc.type_id=='draft':
|
||||
return False
|
||||
|
||||
if not doc.group.type_id == "individ" :
|
||||
return False
|
||||
|
||||
|
@ -204,6 +208,13 @@ def is_individual_draft_author(user, doc):
|
|||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_bofreq_editor(user, doc):
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
if not doc.type_id=='bofreq':
|
||||
return False
|
||||
return user.person in bofreq_editors(doc)
|
||||
|
||||
def openid_userinfo(claims, user):
|
||||
# Populate claims dict.
|
||||
|
|
50
ietf/mailtrigger/migrations/0023_bofreq_triggers.py
Normal file
50
ietf/mailtrigger/migrations/0023_bofreq_triggers.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Copyright The IETF Trust 2021 All Rights Reserved
|
||||
# Generated by Django 2.2.23 on 2021-05-26 07:52
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
|
||||
Recipient = apps.get_model('mailtrigger', 'Recipient')
|
||||
|
||||
Recipient.objects.create(slug='bofreq_editors',desc='BOF request editors',template='')
|
||||
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=['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=['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=['bofreq_responsible', 'bofreq_editors', 'doc_notify']))
|
||||
|
||||
for recipient in Recipient.objects.filter(slug__in=['bofreq_responsible','bofreq_editors']):
|
||||
MailTrigger.objects.get(slug='doc_state_edited').to.add(recipient)
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
|
||||
Recipient = apps.get_model('mailtrigger', 'Recipient')
|
||||
for recipient in Recipient.objects.filter(slug__in=['bofreq_responsible','bofreq_editors']):
|
||||
MailTrigger.objects.get(slug='doc_state_edited').to.remove(recipient)
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
('mailtrigger', '0022_add_doc_external_resource_change_requested'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward,reverse)
|
||||
]
|
|
@ -6,6 +6,8 @@ from django.db import models
|
|||
from django.template import Template, Context
|
||||
|
||||
from email.utils import parseaddr
|
||||
|
||||
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
|
||||
|
@ -392,3 +394,36 @@ class Recipient(models.Model):
|
|||
|
||||
def gather_yang_doctors_secretaries(self, **kwargs):
|
||||
return self.gather_group_secretaries(group=Group.objects.get(acronym='yangdoctors'))
|
||||
|
||||
def gather_bofreq_editors(self, **kwargs):
|
||||
addrs = []
|
||||
if 'doc' in kwargs:
|
||||
bofreq = kwargs['doc']
|
||||
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 = None
|
||||
if previous_responsible in kwargs:
|
||||
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
|
||||
|
||||
|
|
|
@ -68,8 +68,10 @@ def gather_relevant_expansions(**kwargs):
|
|||
|
||||
doc = kwargs['doc']
|
||||
|
||||
# PEY: does this need to include irsg_ballot_saved as well?
|
||||
relevant.update(['doc_state_edited','doc_telechat_details_changed','ballot_deferred','iesg_ballot_saved'])
|
||||
relevant.add('doc_state_edited')
|
||||
|
||||
if not doc.type_id in ['bofreq',]:
|
||||
relevant.update(['doc_telechat_details_changed','ballot_deferred','iesg_ballot_saved'])
|
||||
|
||||
if doc.type_id in ['draft','statchg']:
|
||||
relevant.update(starts_with('last_call_'))
|
||||
|
@ -91,6 +93,9 @@ def gather_relevant_expansions(**kwargs):
|
|||
if doc.type_id == 'charter':
|
||||
relevant.update(['charter_external_review','ballot_approved_charter'])
|
||||
|
||||
if doc.type_id == 'bofreq':
|
||||
relevant.update(starts_with('bofreq'))
|
||||
|
||||
if 'group' in kwargs:
|
||||
|
||||
relevant.update(starts_with('group_'))
|
||||
|
|
|
@ -366,7 +366,7 @@ class Command(BaseCommand):
|
|||
attendees=100,
|
||||
agenda_note="",
|
||||
requested_duration=datetime.timedelta(seconds=7200), # 2:00:00
|
||||
comments="""Must not conflict with Transport Area BoFs. """, # this is implicit
|
||||
comments="""Must not conflict with Transport Area BOFs. """, # this is implicit
|
||||
remote_instructions="",
|
||||
)
|
||||
## session for tsvwg ##
|
||||
|
@ -377,7 +377,7 @@ class Command(BaseCommand):
|
|||
attendees=100,
|
||||
agenda_note="",
|
||||
requested_duration=datetime.timedelta(seconds=7200), # 2:00:00
|
||||
comments="""Must not conflict with Transport Area BoFs. """, # this is implicit
|
||||
comments="""Must not conflict with Transport Area BOFs. """, # this is implicit
|
||||
remote_instructions="",
|
||||
)
|
||||
c = Constraint.objects.create(meeting=m, source=s.group, name_id='conflict', target_id=1665, ) # intarea
|
||||
|
@ -440,7 +440,7 @@ class Command(BaseCommand):
|
|||
attendees=80,
|
||||
agenda_note="Joint with ARTAREA",
|
||||
requested_duration=datetime.timedelta(seconds=7200), # 2:00:00
|
||||
comments=""" and avoid the same kind of conflicts with other area meetings and any Bofs and potential new ART WGs.""", # this is implicit
|
||||
comments=""" and avoid the same kind of conflicts with other area meetings and any BOFs and potential new ART WGs.""", # this is implicit
|
||||
remote_instructions="",
|
||||
)
|
||||
s.joint_with_groups.set(Group.objects.filter(acronym='artarea'))
|
||||
|
@ -2598,7 +2598,7 @@ class Command(BaseCommand):
|
|||
attendees=50,
|
||||
agenda_note="",
|
||||
requested_duration=datetime.timedelta(seconds=5400), # 1:30:00
|
||||
comments="""Please avoid collision with any Sec and IoT-related BoFs.""",
|
||||
comments="""Please avoid collision with any Sec and IoT-related BOFs.""",
|
||||
remote_instructions="",
|
||||
)
|
||||
c = Constraint.objects.create(meeting=m, source=s.group, name_id='conflict', target_id=1187, ) # saag
|
||||
|
|
|
@ -705,24 +705,24 @@ class Session(object):
|
|||
for other in overlapping_sessions:
|
||||
if not other:
|
||||
continue
|
||||
# BoFs cannot conflict with PRGs
|
||||
# BOFs cannot conflict with PRGs
|
||||
if self.is_bof and other.is_prg:
|
||||
violations.append('{}: BoF overlaps with PRG: {}'
|
||||
violations.append('{}: BOF overlaps with PRG: {}'
|
||||
.format(self.group, other.group))
|
||||
cost += self.business_constraint_costs['bof_overlapping_prg']
|
||||
# BoFs cannot conflict with any other BoFs
|
||||
# BOFs cannot conflict with any other BOFs
|
||||
if self.is_bof and other.is_bof:
|
||||
violations.append('{}: BoF overlaps with other BoF: {}'
|
||||
violations.append('{}: BOF overlaps with other BOF: {}'
|
||||
.format(self.group, other.group))
|
||||
cost += self.business_constraint_costs['bof_overlapping_bof']
|
||||
# BoFs cannot conflict with any other WGs in their area
|
||||
# BOFs cannot conflict with any other WGs in their area
|
||||
if self.is_bof and self.parent == other.parent:
|
||||
violations.append('{}: BoF overlaps with other session from same area: {}'
|
||||
violations.append('{}: BOF overlaps with other session from same area: {}'
|
||||
.format(self.group, other.group))
|
||||
cost += self.business_constraint_costs['bof_overlapping_area_wg']
|
||||
# BoFs cannot conflict with any area-wide meetings (of any area)
|
||||
# BOFs cannot conflict with any area-wide meetings (of any area)
|
||||
if self.is_bof and other.is_area_meeting:
|
||||
violations.append('{}: BoF overlaps with area meeting {}'
|
||||
violations.append('{}: BOF overlaps with area meeting {}'
|
||||
.format(self.group, other.group))
|
||||
cost += self.business_constraint_costs['bof_overlapping_area_meeting']
|
||||
# Area meetings cannot conflict with anything else in their area
|
||||
|
|
|
@ -9,22 +9,22 @@ def forward(apps, schema_editor):
|
|||
BusinessConstraint = apps.get_model("meeting", "BusinessConstraint")
|
||||
BusinessConstraint.objects.create(
|
||||
slug="bof_overlapping_prg",
|
||||
name="BoFs cannot conflict with PRGs",
|
||||
name="BOFs cannot conflict with PRGs",
|
||||
penalty=100000,
|
||||
)
|
||||
BusinessConstraint.objects.create(
|
||||
slug="bof_overlapping_bof",
|
||||
name="BoFs cannot conflict with any other BoFs",
|
||||
name="BOFs cannot conflict with any other BOFs",
|
||||
penalty=100000,
|
||||
)
|
||||
BusinessConstraint.objects.create(
|
||||
slug="bof_overlapping_area_wg",
|
||||
name="BoFs cannot conflict with any other WGs in their area",
|
||||
name="BOFs cannot conflict with any other WGs in their area",
|
||||
penalty=100000,
|
||||
)
|
||||
BusinessConstraint.objects.create(
|
||||
slug="bof_overlapping_area_meeting",
|
||||
name="BoFs cannot conflict with any area-wide meetings (of any area)",
|
||||
name="BOFs cannot conflict with any area-wide meetings (of any area)",
|
||||
penalty=10000,
|
||||
)
|
||||
BusinessConstraint.objects.create(
|
||||
|
|
|
@ -752,7 +752,7 @@ class MeetingTests(TestCase):
|
|||
|
||||
# Should be a 'non-area events' link showing appropriate types
|
||||
non_area_labels = [
|
||||
'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
|
||||
'BOF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
|
||||
]
|
||||
self.assertIn('%s?show=%s' % (ical_url, ','.join(non_area_labels).lower()), content)
|
||||
|
||||
|
@ -1029,7 +1029,7 @@ class EditMeetingScheduleTests(TestCase):
|
|||
self.assertEqual(time_labels, time_header_labels)
|
||||
|
||||
def test_bof_session_tag(self):
|
||||
"""Sessions for BoF groups should be marked as such"""
|
||||
"""Sessions for BOF groups should be marked as such"""
|
||||
meeting = MeetingFactory(type_id='ietf')
|
||||
|
||||
non_bof_session = SessionFactory(meeting=meeting)
|
||||
|
@ -1044,13 +1044,13 @@ class EditMeetingScheduleTests(TestCase):
|
|||
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('#session{} .bof-tag'.format(non_bof_session.pk))), 0,
|
||||
'Non-BoF session should not be tagged as a BoF session')
|
||||
'Non-BOF session should not be tagged as a BOF session')
|
||||
|
||||
bof_tags = q('#session{} .bof-tag'.format(bof_session.pk))
|
||||
self.assertEqual(len(bof_tags), 1,
|
||||
'BoF session should have one BoF session tag')
|
||||
self.assertIn('BoF', bof_tags.eq(0).text(),
|
||||
'BoF tag should contain text "BoF"')
|
||||
'BOF session should have one BOF session tag')
|
||||
self.assertIn('BOF', bof_tags.eq(0).text(),
|
||||
'BOF tag should contain text "BOF"')
|
||||
|
||||
def _setup_for_swap_timeslots(self):
|
||||
"""Create a meeting, rooms, and schedule for swap_timeslots testing
|
||||
|
|
|
@ -1552,7 +1552,7 @@ def prepare_filter_keywords(tagged_assignments, group_parents):
|
|||
|
||||
# Keywords that should appear in 'non-area' column
|
||||
non_area_labels = [
|
||||
'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
|
||||
'BOF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
|
||||
]
|
||||
# Remove any unused non-area keywords
|
||||
non_area_filters = [
|
||||
|
@ -4102,7 +4102,7 @@ def request_minutes(request, num=None):
|
|||
body = render_to_string('meeting/request_minutes.txt', body_context)
|
||||
initial = {'to': 'wgchairs@ietf.org',
|
||||
'cc': 'irsg@irtf.org',
|
||||
'subject': 'Request for IETF WG and Bof Session Minutes',
|
||||
'subject': 'Request for IETF WG and BOF Session Minutes',
|
||||
'body': body,
|
||||
}
|
||||
form = RequestMinutesForm(initial=initial)
|
||||
|
|
|
@ -2305,6 +2305,76 @@
|
|||
"model": "doc.state",
|
||||
"pk": 157
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The BOF request is proposed",
|
||||
"name": "Proposed",
|
||||
"next_states": [
|
||||
159,
|
||||
160,
|
||||
161,
|
||||
162
|
||||
],
|
||||
"order": 0,
|
||||
"slug": "proposed",
|
||||
"type": "bofreq",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.state",
|
||||
"pk": 158
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The BOF request is approved",
|
||||
"name": "Approved",
|
||||
"next_states": [],
|
||||
"order": 1,
|
||||
"slug": "approved",
|
||||
"type": "bofreq",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.state",
|
||||
"pk": 159
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The BOF request is declined",
|
||||
"name": "Declined",
|
||||
"next_states": [],
|
||||
"order": 2,
|
||||
"slug": "declined",
|
||||
"type": "bofreq",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.state",
|
||||
"pk": 160
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The BOF request is proposed",
|
||||
"name": "Replaced",
|
||||
"next_states": [],
|
||||
"order": 3,
|
||||
"slug": "replaced",
|
||||
"type": "bofreq",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.state",
|
||||
"pk": 161
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The BOF request is abandoned",
|
||||
"name": "Abandoned",
|
||||
"next_states": [],
|
||||
"order": 4,
|
||||
"slug": "abandoned",
|
||||
"type": "bofreq",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.state",
|
||||
"pk": 162
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"label": "State"
|
||||
|
@ -2319,6 +2389,13 @@
|
|||
"model": "doc.statetype",
|
||||
"pk": "bluesheets"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"label": "BOF Request State"
|
||||
},
|
||||
"model": "doc.statetype",
|
||||
"pk": "bofreq"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"label": "State"
|
||||
|
@ -2625,20 +2702,20 @@
|
|||
"about_page": "ietf.group.views.group_about",
|
||||
"acts_like_wg": false,
|
||||
"admin_roles": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"agenda_type": null,
|
||||
"agenda_type": "ad",
|
||||
"create_wiki": true,
|
||||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
"default_parent": "",
|
||||
"default_tab": "ietf.group.views.group_about",
|
||||
"default_used_roles": "[\n \"ad\",\n \"chair\",\n \"reviewer\",\n \"secr\"\n]",
|
||||
"docman_roles": "[]",
|
||||
"docman_roles": "[\n \"chair\"\n]",
|
||||
"groupman_authroles": "[\n \"Secretariat\"\n]",
|
||||
"groupman_roles": "[\n \"ad\",\n \"secr\"\n]",
|
||||
"has_chartering_process": false,
|
||||
"has_default_jabber": false,
|
||||
"has_documents": false,
|
||||
"has_meetings": false,
|
||||
"has_meetings": true,
|
||||
"has_milestones": false,
|
||||
"has_nonsession_materials": false,
|
||||
"has_reviews": false,
|
||||
|
@ -3451,6 +3528,60 @@
|
|||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "ballot_saved"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [],
|
||||
"desc": "Recipients when the editors of a BOF proposal are changed.",
|
||||
"to": [
|
||||
"bofreq_editors",
|
||||
"bofreq_previous_editors",
|
||||
"bofreq_responsible",
|
||||
"doc_notify"
|
||||
]
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "bofreq_editors_changed"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [],
|
||||
"desc": "Recipients when a new revision of a BOF request is uploaded.",
|
||||
"to": [
|
||||
"bofreq_editors",
|
||||
"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",
|
||||
"bofreq_responsible",
|
||||
"doc_notify"
|
||||
]
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "bofreq_title_changed"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
|
@ -3731,6 +3862,8 @@
|
|||
"cc": [],
|
||||
"desc": "Recipients when a document's state is manually edited",
|
||||
"to": [
|
||||
"bofreq_editors",
|
||||
"bofreq_responsible",
|
||||
"doc_ad",
|
||||
"doc_affecteddoc_authors",
|
||||
"doc_affecteddoc_group_chairs",
|
||||
|
@ -5104,6 +5237,38 @@
|
|||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "sub_replaced_doc_director_approval_requested"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "BOF request editors",
|
||||
"template": ""
|
||||
},
|
||||
"model": "mailtrigger.recipient",
|
||||
"pk": "bofreq_editors"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"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 %}"
|
||||
},
|
||||
"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",
|
||||
|
@ -9751,6 +9916,17 @@
|
|||
"model": "name.doctypename",
|
||||
"pk": "bluesheets"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
"name": "BOF Request",
|
||||
"order": 0,
|
||||
"prefix": "bofreq",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.doctypename",
|
||||
"pk": "bofreq"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
|
@ -12052,6 +12228,16 @@
|
|||
"model": "name.rolename",
|
||||
"pk": "delegate"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Director of Development",
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.rolename",
|
||||
"pk": "devdir"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
|
@ -15299,7 +15485,7 @@
|
|||
"fields": {
|
||||
"command": "xym",
|
||||
"switch": "--version",
|
||||
"time": "2021-06-08T00:12:46.324",
|
||||
"time": "2021-07-13T00:12:25.184",
|
||||
"used": true,
|
||||
"version": "xym 0.5"
|
||||
},
|
||||
|
@ -15310,9 +15496,9 @@
|
|||
"fields": {
|
||||
"command": "pyang",
|
||||
"switch": "--version",
|
||||
"time": "2021-06-08T00:12:48.137",
|
||||
"time": "2021-07-13T00:12:26.721",
|
||||
"used": true,
|
||||
"version": "pyang 2.4.0"
|
||||
"version": "pyang 2.5.0"
|
||||
},
|
||||
"model": "utils.versioninfo",
|
||||
"pk": 2
|
||||
|
@ -15321,7 +15507,7 @@
|
|||
"fields": {
|
||||
"command": "yanglint",
|
||||
"switch": "--version",
|
||||
"time": "2021-06-08T00:12:48.465",
|
||||
"time": "2021-07-13T00:12:27.015",
|
||||
"used": true,
|
||||
"version": "yanglint SO 1.6.7"
|
||||
},
|
||||
|
@ -15332,9 +15518,9 @@
|
|||
"fields": {
|
||||
"command": "xml2rfc",
|
||||
"switch": "--version",
|
||||
"time": "2021-06-08T00:12:51.318",
|
||||
"time": "2021-07-13T00:12:29.814",
|
||||
"used": true,
|
||||
"version": "xml2rfc 3.8.0"
|
||||
"version": "xml2rfc 3.9.1"
|
||||
},
|
||||
"model": "utils.versioninfo",
|
||||
"pk": 4
|
||||
|
|
23
ietf/name/migrations/0027_add_bofrequest.py
Normal file
23
ietf/name/migrations/0027_add_bofrequest.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Copyright The IETF Trust 2021 All Rights Reserved
|
||||
|
||||
# Generated by Django 2.2.23 on 2021-05-21 12:48
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def forward(apps,schema_editor):
|
||||
DocTypeName = apps.get_model('name','DocTypeName')
|
||||
DocTypeName.objects.create(prefix='bofreq', slug='bofreq', name="BOF Request", desc="", used=True, order=0)
|
||||
|
||||
def reverse(apps,schema_editor):
|
||||
DocTypeName = apps.get_model('name','DocTypeName')
|
||||
DocTypeName.objects.filter(slug='bofreq').delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0026_add_conflict_constraintnames'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
|
@ -19,7 +19,7 @@ TO_LIST = ('IETF Announcement List <ietf-announce@ietf.org>',
|
|||
'RFP Announcement List <rfp-announce@ietf.org>',
|
||||
'The IESG <iesg@ietf.org>',
|
||||
'Working Group Chairs <wgchairs@ietf.org>',
|
||||
'BoF Chairs <bofchairs@ietf.org>',
|
||||
'BOF Chairs <bofchairs@ietf.org>',
|
||||
'Other...')
|
||||
|
||||
# ---------------------------------------------
|
||||
|
|
|
@ -645,6 +645,7 @@ DATETIME_FORMAT = "Y-m-d H:i T"
|
|||
# regex can reasonably be expected to be a unique one-off.
|
||||
URL_REGEXPS = {
|
||||
"acronym": r"(?P<acronym>[-a-z0-9]+)",
|
||||
"bofreq": r"(?P<name>bofreq-[-a-z0-9]+)",
|
||||
"charter": r"(?P<name>charter-[-a-z0-9]+)",
|
||||
"date": r"(?P<date>\d{4}-\d{2}-\d{2})",
|
||||
"name": r"(?P<name>[A-Za-z0-9._+-]+?)",
|
||||
|
@ -663,6 +664,7 @@ INTERNET_DRAFT_PATH = '/a/ietfdata/doc/draft/repository'
|
|||
INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/'
|
||||
RFC_PATH = '/a/www/ietf-ftp/rfc/'
|
||||
CHARTER_PATH = '/a/ietfdata/doc/charter/'
|
||||
BOFREQ_PATH = '/a/ietfdata/doc/bofreq/'
|
||||
CONFLICT_REVIEW_PATH = '/a/ietfdata/doc/conflict-review'
|
||||
STATUS_CHANGE_PATH = '/a/ietfdata/doc/status-change'
|
||||
AGENDA_PATH = '/a/www/www6s/proceedings/'
|
||||
|
|
26
ietf/static/ietf/js/upload_bofreq.js
Normal file
26
ietf/static/ietf/js/upload_bofreq.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
$(document).ready(function () {
|
||||
var form = $("form.upload-content");
|
||||
// review submission selection
|
||||
form.find("[name=bofreq_submission]").on("click change", function () {
|
||||
var val = form.find("[name=bofreq_submission]:checked").val();
|
||||
|
||||
var shouldBeVisible = {
|
||||
"enter": ['[name="bofreq_content"]'],
|
||||
"upload": ['[name="bofreq_file"]'],
|
||||
};
|
||||
|
||||
for (var v in shouldBeVisible) {
|
||||
for (var i in shouldBeVisible[v]) {
|
||||
var selector = shouldBeVisible[v][i];
|
||||
var row = form.find(selector);
|
||||
if (!row.is(".form-group"))
|
||||
row = row.closest(".form-group");
|
||||
|
||||
if ($.inArray(selector, shouldBeVisible[val]) != -1)
|
||||
row.show();
|
||||
else
|
||||
row.hide();
|
||||
}
|
||||
}
|
||||
}).trigger("change");
|
||||
});
|
|
@ -27,6 +27,7 @@
|
|||
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>New work</li>
|
||||
<li><a href="{% url "ietf.group.views.chartering_groups" %}">Chartering groups</a></li>
|
||||
<li><a href="{% url "ietf.group.views.bofs" group_type="wg" %}">BOFs</a></li>
|
||||
<li><a href="{% url "ietf.doc.views_bofreq.bof_requests" %}">BOF Requests</a></li>
|
||||
|
||||
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
|
||||
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>Other groups</li>
|
||||
|
|
46
ietf/templates/doc/bofreq/bof_requests.html
Normal file
46
ietf/templates/doc/bofreq/bof_requests.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2021 All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load person_filters %}
|
||||
|
||||
{% block title %}BOF Requests{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
|
||||
<h1>BOF Requests</h1>
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="buttonlist">
|
||||
<a id="start_button" class="btn btn-primary" href="{% url 'ietf.doc.views_bofreq.new_bof_request' %}">Start New BOF Request</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% 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><th>Responsible</th><th>Editors</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>
|
||||
<td>{% for person in req.responsible %}{% person_link person %}{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
||||
<td>{% for person in req.editors %}{% person_link person %}{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
28
ietf/templates/doc/bofreq/bofreq_template.md
Normal file
28
ietf/templates/doc/bofreq/bofreq_template.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Name: Exact MPLS Edges (EXAMPLE) (There's an acronym for anything if you really want one ;-)
|
||||
## Description
|
||||
Replace this with a few paragraphs describing the BOF request.
|
||||
|
||||
Fill in the details below. Keep items in the order they appear here.
|
||||
|
||||
## Required Details
|
||||
- Status: (not) WG Forming
|
||||
- Responsible AD: name
|
||||
- BOF proponents: name <email>, name <email> (1-3 people - who are requesting and coordinating discussion for proposal)
|
||||
- BOF chairs: TBD
|
||||
- Number of people expected to attend: 100
|
||||
- Length of session (1 or 2 hours): 2 hours
|
||||
- Conflicts (whole Areas and/or WGs)
|
||||
- Chair Conflicts: TBD
|
||||
- Technology Overlap: TBD
|
||||
- Key Participant Conflict: TBD
|
||||
## Agenda
|
||||
- Items, drafts, speakers, timing
|
||||
- Or a URL
|
||||
## Links to the mailing list, draft charter if any, relevant Internet-Drafts, etc.
|
||||
- Mailing List: https://www.ietf.org/mailman/listinfo/example
|
||||
- Draft charter: https://datatracker.ietf.org/doc/charter-ietf-EXAMPLE/
|
||||
- Relevant drafts:
|
||||
- Use Cases:
|
||||
- https://datatracker.ietf.org/html/draft-blah-uses
|
||||
- Solutions
|
||||
- https://datatracker.ietf.org/html/draft-blah-soln
|
30
ietf/templates/doc/bofreq/change_editors.html
Normal file
30
ietf/templates/doc/bofreq/change_editors.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 editors for {{doc.name}}{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
{{ form.media.css}}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Change editors<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 %}
|
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 %}
|
28
ietf/templates/doc/bofreq/new_bofreq.html
Normal file
28
ietf/templates/doc/bofreq/new_bofreq.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2021, All Rights Reserved #}
|
||||
{% load origin bootstrap3 static %}
|
||||
|
||||
{% block title %}Start a new BOF Request{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Start a new BOF Request</h1>
|
||||
|
||||
<p>Choose a short descriptive title for your request. Take time to choose a good initial title - it will be used to make the filename for your request's content. The title can be changed later, but the filename will not change.</p>
|
||||
<p>For example, a request with a title of "A new important bit" will be saved as "bofreq-a-new-important-bit-00.md".</p>
|
||||
<form class="upload-content form-horizontal" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form layout="horizontal" %}
|
||||
|
||||
{% buttons %}
|
||||
<a class="btn btn-default" href="{{ doc.get_absolute_url }}">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'ietf/js/upload_bofreq.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
28
ietf/templates/doc/bofreq/upload_content.html
Normal file
28
ietf/templates/doc/bofreq/upload_content.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2021, All Rights Reserved #}
|
||||
{% load origin bootstrap3 static %}
|
||||
|
||||
{% block title %}Upload new revision: {{doc.name}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Upload New Revision<br>
|
||||
<small>{{ doc.name }}</small>
|
||||
</h1>
|
||||
|
||||
<form class="upload-content form-horizontal" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form layout="horizontal" %}
|
||||
|
||||
{% buttons %}
|
||||
<a class="btn btn-default" href="{{ doc.get_absolute_url }}">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'ietf/js/upload_bofreq.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
180
ietf/templates/doc/document_bofreq.html
Normal file
180
ietf/templates/doc/document_bofreq.html
Normal file
|
@ -0,0 +1,180 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2021, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load static %}
|
||||
{% load ietf_filters %}
|
||||
{% load person_filters %}
|
||||
|
||||
{% block pagehead %}
|
||||
<script src="{% static 'd3/d3.min.js' %}"></script>
|
||||
<script src="{% static 'jquery/jquery.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/document_timeline.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ doc.title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
{{ top|safe }}
|
||||
|
||||
{% include "doc/revisions_list.html" %}
|
||||
<div id="timeline"></div>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead id="message-row">
|
||||
<tr>
|
||||
{% if doc.rev != latest_rev %}
|
||||
<th colspan="4" class="alert-warning">The information below is for an older version of this BOF request</th>
|
||||
{% else %}
|
||||
<th colspan="4"></th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="meta">
|
||||
|
||||
<tr>
|
||||
<th>Document</th>
|
||||
<th>Type</th>
|
||||
<td class="edit"></td>
|
||||
<td>
|
||||
{{doc.get_state.slug|capfirst}} BOF request
|
||||
{% if snapshot %}
|
||||
<span class="label label-warning">Snapshot</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<th>Title</th>
|
||||
<td class="edit">
|
||||
{% if not snapshot %}
|
||||
{% if editor_can_manage or can_manage %}
|
||||
{% doc_edit_button 'ietf.doc.views_bofreq.edit_title' name=doc.name %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ doc.title }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<th>Last updated</th>
|
||||
<td class="edit"></td>
|
||||
<td>{{ doc.time|date:"Y-m-d" }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<th><a href="{% url 'ietf.doc.views_help.state_help' type='bofreq' %}">State</a></th>
|
||||
<td class="edit">
|
||||
{% if not snapshot and can_manage %}
|
||||
{% doc_edit_button 'ietf.doc.views_bofreq.change_state' name=doc.name %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if doc.get_state %}
|
||||
<span title="{{ doc.get_state.desc }}">{{ doc.get_state.name }}</span>
|
||||
{% else %}
|
||||
No document state
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr id="editors">
|
||||
<td></td>
|
||||
<th>Editor{{editors|pluralize}}</th>
|
||||
<td class="edit">
|
||||
{% if not snapshot %}
|
||||
{% if editor_can_manage or can_manage %}
|
||||
{% doc_edit_button 'ietf.doc.views_bofreq.change_editors' name=doc.name %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% for editor in editors %}
|
||||
{% person_link editor %}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</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>
|
||||
</tr>
|
||||
|
||||
{% with doc.docextresource_set.all as resources %}
|
||||
{% if resources or editor_can_manage or can_manage %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<th>Additional Resources</th>
|
||||
<td class="edit">
|
||||
{% if editor_can_manage or can_manage %}
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.edit_doc_extresources' name=doc.name %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if resources %}
|
||||
<table class="col-md-12 col-sm-12 col-xs-12">
|
||||
<tbody>
|
||||
{% for resource in resources|dictsort:"display_name" %}
|
||||
{% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %}
|
||||
<tr><td> - <a href="{{ resource.value }}" title="{{resource.name.name}}">{% firstof resource.display_name resource.name.name %}</a></td></tr>
|
||||
{# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #}
|
||||
{% else %}
|
||||
<tr><td> - <span title="{{resource.name.name}}">{% firstof resource.display_name resource.name.name %}: {{resource.value}}</span></td></tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<th>Send notices to</th>
|
||||
<td class="edit">
|
||||
{% if not snapshot %}
|
||||
{% if can_manage %}
|
||||
{% doc_edit_button 'ietf.doc.views_doc.edit_notify' name=doc.name %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ doc.notify|default:"(None)" }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if not snapshot %}
|
||||
{% if editor_can_manage or can_manage %}
|
||||
<p id="change-request"><a class="btn btn-default" href="{% url 'ietf.doc.views_bofreq.submit' name=doc.name %}">Change BOF request text</a></p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{doc.name}}-{{doc.rev}}</div>
|
||||
<div class="panel-body">
|
||||
{{ content|sanitize|safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
6
ietf/templates/doc/mail/bofreq_editors_changed.txt
Normal file
6
ietf/templates/doc/mail/bofreq_editors_changed.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% autoescape off %}{{request.user.person.name}} has changed the editors for {{bofreq.name}}-{{bofreq.rev}}
|
||||
|
||||
The previous editors were : {{previous_editors|join:", "}}
|
||||
|
||||
The new editors are : {{editors|join:", "}}
|
||||
{% endautoescape %}
|
3
ietf/templates/doc/mail/bofreq_new_revision.txt
Normal file
3
ietf/templates/doc/mail/bofreq_new_revision.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
{% autoescape off %}{{request.user.person.name}} has uploaded {{bofreq.name}}-{{bofreq.rev}}
|
||||
See {{settings.IDTRACKER_BASE_URL}}{{bofreq.get_absolute_url}}
|
||||
{% endautoescape %}
|
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 %}
|
3
ietf/templates/doc/mail/bofreq_title_changed.txt
Normal file
3
ietf/templates/doc/mail/bofreq_title_changed.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
{% autoescape off %}{{request.user.person.name}} has changed the title for {{bofreq.name}}-{{bofreq.rev}} to
|
||||
"{{bofreq.title}}"
|
||||
{% endautoescape %}
|
|
@ -1,7 +1,7 @@
|
|||
<div id="session{{ session.pk }}" class="session {% if not session.group.parent.scheduling_color %}untoggleable{% endif %} {% if session.parent_acronym %}parent-{{ session.parent_acronym }}{% endif %} {% if session.readonly %}readonly{% endif %}" style="width:{{ session.layout_width }}em;" data-duration="{{ session.requested_duration.total_seconds }}" {% if session.attendees != None %}data-attendees="{{ session.attendees }}"{% endif %}>
|
||||
<div class="session-label {% if session.group and session.group.is_bof %}bof-session{% endif %}">
|
||||
{{ session.scheduling_label }}
|
||||
{% if session.group and session.group.is_bof %}<span class="bof-tag">BoF</span>{% endif %}
|
||||
{% if session.group and session.group.is_bof %}<span class="bof-tag">BOF</span>{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -32,7 +32,7 @@
|
|||
{{ session.scheduling_label }}
|
||||
· {{ session.requested_duration_in_hours }}h
|
||||
{% if session.group %}
|
||||
· {% if session.group.is_bof %}BoF{% else %}{{ session.group.type.name }}{% endif %}
|
||||
· {% if session.group.is_bof %}BOF{% else %}{{ session.group.type.name }}{% endif %}
|
||||
{% endif %}
|
||||
{% if session.attendees != None %}
|
||||
· {{ session.attendees }} <i class="fa fa-user-o"></i>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
{% if d.name.slug == 'opensched' %}
|
||||
To request a Working Group session, use the
|
||||
<a href="{% url 'ietf.secr.sreq.views.main' %}">IETF Meeting Session Request Tool</a>.
|
||||
If you are working on a BoF request, it is highly recommended
|
||||
If you are working on a BOF request, it is highly recommended
|
||||
to tell the IESG now by sending an email to
|
||||
<a href="mailto:iesg@ietf.org">iesg@ietf.org</a> to get advance help with the request.
|
||||
{% endif %}
|
||||
|
|
|
@ -9,7 +9,7 @@ DESCRIPTION:{{ d.name.desc }}{% if first and d.name.slug == 'openreg' or first a
|
|||
Register here: https://www.ietf.org/how/meetings/register/{% endif %}{% if d.name.slug == 'opensched' %}\n
|
||||
To request a Working Group session, use the IETF Meeting Session Request Tool:\n
|
||||
{{ request.scheme }}://{{ request.get_host}}{% url 'ietf.secr.sreq.views.main' %}\n
|
||||
If you are working on a BoF request, it is highly recommended to tell the IESG\n
|
||||
If you are working on a BOF request, it is highly recommended to tell the IESG\n
|
||||
now by sending an email to iesg@ietf.org to get advance help with the request.{% endif %}{% if d.name.slug == 'cutoffwgreq' %}\n
|
||||
To request a Working Group session, use the IETF Meeting Session Request Tool:\n
|
||||
{{ request.scheme }}://{{ request.get_host }}{% url 'ietf.secr.sreq.views.main' %}{% endif %}{% if d.name.slug == 'cutoffbofreq' %}\n
|
||||
|
|
|
@ -16,6 +16,6 @@ manual posting.
|
|||
Groups that are missing minutes:{% for group in needs_minutes %}{% ifchanged group.parent %}
|
||||
|
||||
{{group.parent.name}}:{% endifchanged %}
|
||||
{{ group.acronym | upper }}{% if group.state_id == 'bof' %} (BoF){% endif %}{% endfor %}
|
||||
{{ group.acronym | upper }}{% if group.state_id == 'bof' %} (BOF){% endif %}{% endfor %}
|
||||
|
||||
{% endautoescape %}
|
||||
|
|
|
@ -75,6 +75,7 @@ def make_immutable_base_data():
|
|||
|
||||
iab = create_group(name="Internet Architecture Board", acronym="iab", type_id="ietf", parent=ietf)
|
||||
create_person(iab, "chair")
|
||||
create_person(iab, "member")
|
||||
|
||||
ise = create_group(name="Independent Submission Editor", acronym="ise", type_id="rfcedtyp")
|
||||
create_person(ise, "chair")
|
||||
|
|
|
@ -36,6 +36,7 @@ jsonfield>=3.0 # for SubmissionCheck. This is https://github.com/bradjasper/dj
|
|||
jwcrypto>=0.4.0 # for signed notifications
|
||||
logging_tree>=1.8.1
|
||||
lxml>=3.4.0,<5
|
||||
markdown>=3.3.4
|
||||
markdown2>=2.3.8
|
||||
mock>=2.0.0
|
||||
mypy>=0.782,<0.790 # Version requirements determined by django-stubs.
|
||||
|
|
Loading…
Reference in a new issue