Staging for merge forward
- Legacy-Id: 19199
This commit is contained in:
parent
bfad845662
commit
6b383255ad
|
@ -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 )
|
||||
|
||||
from ietf.utils.validators import validate_external_resource_value
|
||||
|
||||
|
@ -192,6 +193,10 @@ 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 DocumentUrlAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'doc', 'tag', 'url', 'desc', ]
|
||||
|
|
|
@ -13,8 +13,11 @@ 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 )
|
||||
from ietf.group.models import Group
|
||||
from ietf.person.factories import PersonFactory
|
||||
from ietf.utils.text import xslugify
|
||||
|
||||
|
||||
def draft_name_generator(type_id,group,n):
|
||||
return '%s-%s-%s-%s%d'%(
|
||||
|
@ -379,3 +382,39 @@ 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 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')
|
||||
|
||||
@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'))
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from ietf.doc.templatetags.mail_filters import std_level_prompt
|
|||
from ietf.utils import log
|
||||
from ietf.utils.mail import send_mail, send_mail_text
|
||||
from ietf.ipr.utils import iprs_from_docs, related_docs
|
||||
from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent
|
||||
from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent, BofreqEditorDocEvent
|
||||
from ietf.doc.utils import needed_ballot_positions
|
||||
from ietf.group.models import Role
|
||||
from ietf.doc.models import Document
|
||||
|
@ -689,3 +689,30 @@ 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 email_bofreq_editors_changed(request, bofreq, previous_editors):
|
||||
editors = bofreq.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
addrs = gather_address_lists('bofreq_editors_changed', doc=bofreq, previous_editors=previous_editors)
|
||||
|
||||
send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
|
||||
f'BOF Request editors changed : {bofreq.name}',
|
||||
'doc/mail/bofreq_editors_changed.txt',
|
||||
dict(bofreq=bofreq, request=request, editors=editors, previous_editors=previous_editors),
|
||||
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', '0024_add_bofrequest'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
23
ietf/doc/migrations/0043_bofreqeditordocevent.py
Normal file
23
ietf/doc/migrations/0043_bofreqeditordocevent.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.23 on 2021-05-25 12:02
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('person', '0018_auto_20201109_0439'),
|
||||
('doc', '0042_bofreq_states'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BofreqEditorDocEvent',
|
||||
fields=[
|
||||
('docevent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='doc.DocEvent')),
|
||||
('editors', models.ManyToManyField(blank=True, to='person.Person')),
|
||||
],
|
||||
bases=('doc.docevent',),
|
||||
),
|
||||
]
|
|
@ -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,6 @@ 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):
|
||||
editors = models.ManyToManyField('person.Person',blank=True)
|
285
ietf/doc/tests_bofreq.py
Normal file
285
ietf/doc/tests_bofreq.py
Normal file
|
@ -0,0 +1,285 @@
|
|||
# Copyright The IETF Trust 2021 All Rights Reserved
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
import io
|
||||
import shutil
|
||||
import os
|
||||
|
||||
from pyquery import PyQuery
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse as urlreverse
|
||||
|
||||
from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory
|
||||
from ietf.doc.models import State, BofreqEditorDocEvent, Document, DocAlias, NewRevisionDocEvent
|
||||
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):
|
||||
states = State.objects.filter(type_id='bofreq')
|
||||
self.assertTrue(states.count()>0)
|
||||
reqs = BofreqFactory.create_batch(states.count())
|
||||
url = urlreverse('ietf.doc.views_bofreq.bof_requests')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('#bofreqs-proposed tbody tr')), states.count())
|
||||
for i in range(states.count()):
|
||||
reqs[i].set_state(states[i])
|
||||
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} tbody tr')), 1)
|
||||
|
||||
|
||||
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 = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=doc))
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r,'Version: 01',status_code=200)
|
||||
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.name,editor_row)
|
||||
for user in ('secretary','ad'):
|
||||
self.client.login(username=user,password=user+"+password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(4, len(q('td.edit>a.btn')))
|
||||
self.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(2, 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 = doc.latest_event(BofreqEditorDocEvent).editors.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', 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 = doc.latest_event(BofreqEditorDocEvent).editors.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'):
|
||||
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(doc.latest_event(BofreqEditorDocEvent).editors.all())
|
||||
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 = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
self.assertEqual(set(previous_editors),set(editors))
|
||||
nobody = PersonFactory()
|
||||
self.client.login(username=nobody.user.username,password=nobody.user.username+'+password')
|
||||
r = self.client.post(url, postdict)
|
||||
self.assertEqual(r.status_code,403)
|
||||
editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
self.assertEqual(set(previous_editors),set(editors))
|
||||
self.client.logout()
|
||||
for username in (previous_editors[0].user.username, 'secretary', 'ad'):
|
||||
empty_outbox()
|
||||
self.client.login(username=username,password=username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
for editor in previous_editors:
|
||||
unescaped = unicontent(r).encode('utf-8').decode('unicode-escape')
|
||||
self.assertIn(editor.name,unescaped)
|
||||
new_editors = set(previous_editors)
|
||||
new_editors.discard(acting_editor)
|
||||
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 = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
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_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 = doc.latest_event(BofreqEditorDocEvent).editors.first()
|
||||
for username in ('secretary', 'ad', 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)
|
||||
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.latest_event(BofreqEditorDocEvent).editors.all()), [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'))
|
|
@ -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),
|
||||
|
@ -144,6 +146,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')),
|
||||
|
|
12
ietf/doc/urls_bofreq.py
Normal file
12
ietf/doc/urls_bofreq.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
from ietf.doc import views_bofreq, views_doc
|
||||
from ietf.utils.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^notices/$', views_doc.edit_notify, name='ietf.doc.views_doc.edit_notify;bofreq'),
|
||||
url(r'^relations/$', views_bofreq.edit_relations),
|
||||
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),
|
||||
]
|
296
ietf/doc/views_bofreq.py
Normal file
296
ietf/doc/views_bofreq.py
Normal file
|
@ -0,0 +1,296 @@
|
|||
# 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, )
|
||||
from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, BofreqEditorDocEvent, State
|
||||
from ietf.doc.utils import add_state_change_event
|
||||
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)
|
||||
return render(request, 'doc/bofreq/bof_requests.html',dict(reqs=reqs))
|
||||
|
||||
def edit_relations(request, name):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
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", "")
|
||||
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.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
if not (has_role(request.user,('Secretariat','Area Director')) or 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.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
if not (has_role(request.user,('Secretariat','Area Director')) or 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 ChangeTitleForm(forms.Form):
|
||||
title = forms.CharField(max_length=255, label="Title", required=True)
|
||||
|
||||
@login_required
|
||||
def edit_title(request, name):
|
||||
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||
editors = bofreq.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
if not (has_role(request.user,('Secretariat','Area Director')) or 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")
|
||||
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,7 +55,8 @@ 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,
|
||||
BofreqEditorDocEvent )
|
||||
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,
|
||||
|
@ -526,6 +528,25 @@ def document_main(request, name, rev=None):
|
|||
can_manage=can_manage,
|
||||
))
|
||||
|
||||
if doc.type_id == "bofreq":
|
||||
# content = markdown2.markdown(doc.text_or_error(),extras=['tables'])
|
||||
content = markdown.markdown(doc.text_or_error(),extensions=['extra'])
|
||||
editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
||||
can_manage = has_role(request.user,['Secretariat','Area Director'])
|
||||
is_editor = 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,
|
||||
is_editor=is_editor,
|
||||
))
|
||||
|
||||
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)
|
||||
|
||||
|
|
38
ietf/mailtrigger/migrations/0023_bofreq_triggers.py
Normal file
38
ietf/mailtrigger/migrations/0023_bofreq_triggers.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# 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 %}')
|
||||
|
||||
mt = MailTrigger.objects.create(slug='bofreq_title_changed',desc='Recipients when the title of a BOF proposal is changed.')
|
||||
mt.to.set(Recipient.objects.filter(slug__in=['doc_ad', 'bofreq_editors', 'doc_notify']))
|
||||
|
||||
mt = MailTrigger.objects.create(slug='bofreq_editors_changed',desc='Recipients when the editors of a BOF proposal are changed.')
|
||||
mt.to.set(Recipient.objects.filter(slug__in=['doc_ad', 'bofreq_editors', 'bofreq_previous_editors', 'doc_notify']))
|
||||
|
||||
mt = MailTrigger.objects.create(slug='bofreq_new_revision', desc='Recipients when a new revision of a BOF request is uploaded.')
|
||||
mt.to.set(Recipient.objects.filter(slug__in=['doc_ad', 'bofreq_editors', 'doc_notify']))
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
|
||||
Recipient = apps.get_model('mailtrigger', 'Recipient')
|
||||
MailTrigger.objects.filter(slug__in=('bofreq_title_changed','bofreq_editors_changed','bofreq_new_revision')).delete()
|
||||
Recipient.objects.filter(slug__in=('bofreq_editors','bofreq_previous_editors')).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailtrigger', '0022_add_doc_external_resource_change_requested'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward,reverse)
|
||||
]
|
|
@ -6,6 +6,7 @@ from django.db import models
|
|||
from django.template import Template, Context
|
||||
|
||||
from email.utils import parseaddr
|
||||
from ietf.doc.models import BofreqEditorDocEvent
|
||||
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 +393,13 @@ 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']
|
||||
editor_event = bofreq.latest_event(BofreqEditorDocEvent)
|
||||
if editor_event:
|
||||
addrs.extend([editor.email_address() for editor in editor_event.editors.all()])
|
||||
return addrs
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -2990,7 +3067,7 @@
|
|||
"about_page": "ietf.group.views.group_about",
|
||||
"acts_like_wg": false,
|
||||
"admin_roles": "[\n \"chair\",\n \"secr\"\n]",
|
||||
"agenda_type": null,
|
||||
"agenda_type": "ietf",
|
||||
"create_wiki": true,
|
||||
"custom_group_roles": true,
|
||||
"customize_workflow": false,
|
||||
|
@ -3002,7 +3079,7 @@
|
|||
"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": true,
|
||||
|
@ -3349,6 +3426,46 @@
|
|||
"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",
|
||||
"doc_ad",
|
||||
"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",
|
||||
"doc_ad",
|
||||
"doc_notify"
|
||||
]
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "bofreq_new_revision"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [],
|
||||
"desc": "Recipients when the title of a BOF proposal is changed.",
|
||||
"to": [
|
||||
"bofreq_editors",
|
||||
"doc_ad",
|
||||
"doc_notify"
|
||||
]
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "bofreq_title_changed"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
|
@ -4063,6 +4180,7 @@
|
|||
"fields": {
|
||||
"cc": [
|
||||
"liaison_cc",
|
||||
"liaison_coordinators",
|
||||
"liaison_response_contacts",
|
||||
"liaison_technical_contacts"
|
||||
],
|
||||
|
@ -5001,6 +5119,22 @@
|
|||
"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": "The person providing a comment to nomcom",
|
||||
|
@ -5401,6 +5535,14 @@
|
|||
"model": "mailtrigger.recipient",
|
||||
"pk": "liaison_cc"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The IAB liaison coordination team members",
|
||||
"template": "<liaison-coordination@iab.org>"
|
||||
},
|
||||
"model": "mailtrigger.recipient",
|
||||
"pk": "liaison_coordinators"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The assigned liaison manager for an external group ",
|
||||
|
@ -9594,6 +9736,17 @@
|
|||
"model": "name.doctypename",
|
||||
"pk": "bluesheets"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Bof Request",
|
||||
"order": 0,
|
||||
"prefix": "bofreq",
|
||||
"used": true
|
||||
},
|
||||
"model": "name.doctypename",
|
||||
"pk": "bofreq"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
|
@ -9901,6 +10054,7 @@
|
|||
"auth",
|
||||
"aut-appr",
|
||||
"grp-appr",
|
||||
"ad-appr",
|
||||
"manual",
|
||||
"cancel"
|
||||
],
|
||||
|
@ -11948,7 +12102,7 @@
|
|||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Liaison CC Contact",
|
||||
"order": 0,
|
||||
"order": 9,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.rolename",
|
||||
|
@ -11958,7 +12112,7 @@
|
|||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Liaison Contact",
|
||||
"order": 0,
|
||||
"order": 8,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.rolename",
|
||||
|
@ -12386,7 +12540,7 @@
|
|||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "IAB stream",
|
||||
"desc": "Internet Architecture Board (IAB)",
|
||||
"name": "IAB",
|
||||
"order": 4,
|
||||
"used": true
|
||||
|
@ -12396,7 +12550,7 @@
|
|||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "IETF stream",
|
||||
"desc": "Internet Engineering Task Force (IETF)",
|
||||
"name": "IETF",
|
||||
"order": 1,
|
||||
"used": true
|
||||
|
@ -12406,7 +12560,7 @@
|
|||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "IRTF Stream",
|
||||
"desc": "Internet Research Task Force (IRTF)",
|
||||
"name": "IRTF",
|
||||
"order": 3,
|
||||
"used": true
|
||||
|
@ -12416,7 +12570,7 @@
|
|||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Independent Submission Editor stream",
|
||||
"desc": "Independent Submission",
|
||||
"name": "ISE",
|
||||
"order": 2,
|
||||
"used": true
|
||||
|
@ -15141,9 +15295,9 @@
|
|||
"fields": {
|
||||
"command": "xym",
|
||||
"switch": "--version",
|
||||
"time": "2020-11-14T00:10:15.888",
|
||||
"time": "2021-05-20T00:12:38.672",
|
||||
"used": true,
|
||||
"version": "xym 0.4.8"
|
||||
"version": "xym 0.5"
|
||||
},
|
||||
"model": "utils.versioninfo",
|
||||
"pk": 1
|
||||
|
@ -15152,7 +15306,7 @@
|
|||
"fields": {
|
||||
"command": "pyang",
|
||||
"switch": "--version",
|
||||
"time": "2020-11-14T00:10:17.069",
|
||||
"time": "2021-05-20T00:12:40.445",
|
||||
"used": true,
|
||||
"version": "pyang 2.4.0"
|
||||
},
|
||||
|
@ -15163,7 +15317,7 @@
|
|||
"fields": {
|
||||
"command": "yanglint",
|
||||
"switch": "--version",
|
||||
"time": "2020-11-14T00:10:17.405",
|
||||
"time": "2021-05-20T00:12:40.647",
|
||||
"used": true,
|
||||
"version": "yanglint SO 1.6.7"
|
||||
},
|
||||
|
@ -15174,9 +15328,9 @@
|
|||
"fields": {
|
||||
"command": "xml2rfc",
|
||||
"switch": "--version",
|
||||
"time": "2020-11-14T00:10:19.405",
|
||||
"time": "2021-05-20T00:12:43.645",
|
||||
"used": true,
|
||||
"version": "xml2rfc 3.4.0"
|
||||
"version": "xml2rfc 3.7.0"
|
||||
},
|
||||
"model": "utils.versioninfo",
|
||||
"pk": 4
|
||||
|
|
23
ietf/name/migrations/0024_add_bofrequest.py
Normal file
23
ietf/name/migrations/0024_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', '0023_change_stream_descriptions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse)
|
||||
]
|
|
@ -644,6 +644,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._+-]+?)",
|
||||
|
@ -662,6 +663,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>
|
||||
|
|
32
ietf/templates/doc/bofreq/bof_requests.html
Normal file
32
ietf/templates/doc/bofreq/bof_requests.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2021 All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block title %}BOF Requests{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
|
||||
{% regroup reqs by get_state_slug as grouped_reqs %}
|
||||
{% for req_group in grouped_reqs %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{req_group.grouper|capfirst}} BOF Requests</div>
|
||||
<div class="panel-body">
|
||||
<table id="bofreqs-{{req_group.grouper}}" class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr><th class="col-sm-4">Name</th><th class="col-sm-1">Date</th><th>Title</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in req_group.list %}
|
||||
<tr>
|
||||
<td><a href={% url 'ietf.doc.views_doc.document_main' name=req.name %}>{{req.name}}-{{req.rev}}</a></td>
|
||||
<td>{{req.latest_revision_event.time|date:"Y-m-d"}}</td>
|
||||
<td>{{req.title}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% 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 %}
|
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 %}
|
||||
|
137
ietf/templates/doc/document_bofreq.html
Normal file
137
ietf/templates/doc/document_bofreq.html
Normal file
|
@ -0,0 +1,137 @@
|
|||
{% 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 is_editor 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 is_editor 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>
|
||||
|
||||
</tbody>
|
||||
|
||||
<tbody class="meta">
|
||||
|
||||
<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;bofreq' name=doc.name %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ doc.notify|default:"(None)" }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if not snapshot %}
|
||||
{% if is_editor 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 %}
|
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 %}
|
|
@ -93,7 +93,6 @@ def reload_db_objects(*objects):
|
|||
"""Rerequest the given arguments from the database so they're refreshed, to be used like
|
||||
|
||||
foo, bar = reload_db_objects(foo, bar)"""
|
||||
|
||||
t = tuple(o.__class__.objects.get(pk=o.pk) for o in objects)
|
||||
if len(objects) == 1:
|
||||
return t[0]
|
||||
|
|
|
@ -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