feat: Move IESG agenda items from filesystem to DB (#5366)
* feat: Add TelechatAgendaContent model and related support * feat: Add UI for managing TelechatAgendaContents * refactor: Rename _view view to _manage * feat: Add a view to dump the TelechatAgendaContent as text/plain * refactor: Point agenda_data() helpers at content in the DB * refactor: Replace references to settings URLs/paths with new plumbing * chore: Remove now-obsolete settings from settings.py * feat: Link to telechat_agenda_content_manage view from iesg agenda * fix: Use correct view name * feat: Link from agenda content management page to IESG agenda view * chore: Create resources * chore: Add new names to names.json * chore: Renumber migration after rebase * chore: Remove unused import * fix: Clean up partially removed code * chore: Add admin model for TelechatAgendaContent * chore: Simplify __str__ method for TelechatAgendaContent * test: Add TelechatAgendaContentFactory * test: Test the fill_in_agenda_administrivia() function * test: Test that agenda contains action_items content * test: Test that sensitive agenda links are restricted by role * test: Test the telechat_agenda_content_view view * test: Add test of telechat_agenda_content_edit view * fix: Add type attribute to button to satisfy html validator * test: Filter TelechatAgendaSectionName to used=True for tests * test: More thoroughly test for likely(ish) permission errors * fix: Fix typo in "tablist" role * test: Test telechat_agenda_content_manage view * style: Put back newlines at EOF * chore: Add admin for TelechatAgendaSectionName * chore: Renumber migrations * fix: Depend on the correct migration Forgot to update the number, but was also depending on the wrong migration.
This commit is contained in:
parent
24277f6ff2
commit
372891194e
|
@ -3,7 +3,7 @@ from django.contrib import admin
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import TelechatDocEvent
|
||||
from ietf.iesg.models import TelechatDate, TelechatAgendaItem
|
||||
from ietf.iesg.models import TelechatDate, TelechatAgendaItem, TelechatAgendaContent
|
||||
|
||||
class TelechatAgendaItemAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
@ -22,3 +22,6 @@ class TelechatDateAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(TelechatDate, TelechatDateAdmin)
|
||||
|
||||
class TelechatAgendaContentAdmin(admin.ModelAdmin):
|
||||
list_display = ('section',)
|
||||
admin.site.register(TelechatAgendaContent, TelechatAgendaContentAdmin)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
# utilities for constructing agendas for IESG telechats
|
||||
|
||||
import io
|
||||
import datetime
|
||||
from collections import OrderedDict
|
||||
|
||||
|
@ -15,7 +14,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.doc.models import Document, LastCallDocEvent, ConsensusDocEvent
|
||||
from ietf.doc.utils_search import fill_in_telechat_date
|
||||
from ietf.iesg.models import TelechatDate, TelechatAgendaItem
|
||||
from ietf.iesg.models import TelechatDate, TelechatAgendaItem, TelechatAgendaContent
|
||||
from ietf.review.utils import review_assignments_to_list_for_docs
|
||||
from ietf.utils.timezone import date_today, make_aware
|
||||
|
||||
|
@ -140,20 +139,18 @@ def agenda_sections():
|
|||
])
|
||||
|
||||
def fill_in_agenda_administrivia(date, sections):
|
||||
extra_info_files = (
|
||||
("1.1", "roll_call", settings.IESG_ROLL_CALL_FILE),
|
||||
("1.3", "minutes", settings.IESG_MINUTES_FILE),
|
||||
("1.4", "action_items", settings.IESG_TASK_FILE),
|
||||
)
|
||||
extra_info = (
|
||||
("1.1", "roll_call"),
|
||||
("1.3", "minutes"),
|
||||
("1.4", "action_items"),
|
||||
)
|
||||
|
||||
for s, key, filename in extra_info_files:
|
||||
for s, key in extra_info:
|
||||
try:
|
||||
with io.open(filename, 'r', encoding='utf-8', errors='replace') as f:
|
||||
t = f.read().strip()
|
||||
except IOError:
|
||||
t = "(Error reading %s)" % filename
|
||||
|
||||
sections[s]["text"] = t
|
||||
text = TelechatAgendaContent.objects.get(section__slug=key).text
|
||||
except TelechatAgendaContent.DoesNotExist:
|
||||
text = ""
|
||||
sections[s]["text"] = text
|
||||
|
||||
def fill_in_agenda_docs(date, sections, docs=None):
|
||||
if not docs:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import debug # pyflakes:ignore
|
||||
import factory
|
||||
|
||||
from ietf.iesg.models import TelechatAgendaItem
|
||||
from ietf.iesg.models import TelechatAgendaItem, TelechatAgendaContent
|
||||
|
||||
|
||||
class IESGMgmtItemFactory(factory.django.DjangoModelFactory):
|
||||
|
@ -14,3 +14,10 @@ class IESGMgmtItemFactory(factory.django.DjangoModelFactory):
|
|||
type = 3
|
||||
text = factory.Faker('paragraph', nb_sentences=3)
|
||||
title = factory.Faker('sentence', nb_words=3)
|
||||
|
||||
|
||||
class TelechatAgendaContentFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = TelechatAgendaContent
|
||||
|
||||
text = factory.Faker('paragraph', nb_sentences=5)
|
||||
|
|
23
ietf/iesg/migrations/0002_telechatagendacontent.py
Normal file
23
ietf/iesg/migrations/0002_telechatagendacontent.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.28 on 2023-03-10 16:47
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0002_telechatagendasectionname'),
|
||||
('iesg', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TelechatAgendaContent',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('text', models.TextField(blank=True)),
|
||||
('section', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='name.TelechatAgendaSectionName')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -39,6 +39,7 @@ import datetime
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from ietf.name.models import TelechatAgendaSectionName
|
||||
from ietf.utils.timezone import date_today
|
||||
|
||||
|
||||
|
@ -95,3 +96,11 @@ class TelechatDate(models.Model):
|
|||
indexes = [
|
||||
models.Index(fields=['-date',]),
|
||||
]
|
||||
|
||||
|
||||
class TelechatAgendaContent(models.Model):
|
||||
section = models.ForeignKey(TelechatAgendaSectionName, on_delete=models.PROTECT)
|
||||
text = models.TextField(blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.section.name} content"
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
# Autogenerated by the mkresources management command 2014-11-13 23:53
|
||||
|
||||
|
||||
from ietf.api import ModelResource
|
||||
from tastypie.constants import ALL
|
||||
from ietf.api import ModelResource, ToOneField
|
||||
from tastypie.constants import ALL, ALL_WITH_RELATIONS
|
||||
from tastypie.cache import SimpleCache
|
||||
|
||||
from ietf import api
|
||||
|
||||
from ietf.iesg.models import TelechatDate, Telechat, TelechatAgendaItem
|
||||
from ietf.iesg.models import TelechatDate, Telechat, TelechatAgendaItem, TelechatAgendaContent
|
||||
|
||||
|
||||
class TelechatDateResource(ModelResource):
|
||||
|
@ -59,3 +59,20 @@ class TelechatAgendaItemResource(ModelResource):
|
|||
}
|
||||
api.iesg.register(TelechatAgendaItemResource())
|
||||
|
||||
|
||||
|
||||
from ietf.name.resources import TelechatAgendaSectionNameResource
|
||||
class TelechatAgendaContentResource(ModelResource):
|
||||
section = ToOneField(TelechatAgendaSectionNameResource, 'section')
|
||||
class Meta:
|
||||
queryset = TelechatAgendaContent.objects.none()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'telechatagendacontent'
|
||||
ordering = ['id', ]
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"text": ALL,
|
||||
"section": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.iesg.register(TelechatAgendaContentResource())
|
||||
|
|
|
@ -22,12 +22,12 @@ from ietf.doc.factories import WgDraftFactory, IndividualDraftFactory, ConflictR
|
|||
from ietf.doc.utils import create_ballot_if_not_open
|
||||
from ietf.group.factories import RoleFactory, GroupFactory
|
||||
from ietf.group.models import Group, GroupMilestone, Role
|
||||
from ietf.iesg.agenda import get_agenda_date, agenda_data
|
||||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.name.models import StreamName
|
||||
from ietf.iesg.agenda import get_agenda_date, agenda_data, fill_in_agenda_administrivia, agenda_sections
|
||||
from ietf.iesg.models import TelechatDate, TelechatAgendaContent
|
||||
from ietf.name.models import StreamName, TelechatAgendaSectionName
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
|
||||
from ietf.iesg.factories import IESGMgmtItemFactory
|
||||
from ietf.iesg.factories import IESGMgmtItemFactory, TelechatAgendaContentFactory
|
||||
from ietf.utils.timezone import date_today, DEADLINE_TZINFO
|
||||
|
||||
|
||||
|
@ -141,6 +141,16 @@ class IESGAgendaTests(TestCase):
|
|||
for i in range(0, 10):
|
||||
self.mgmt_items.append(IESGMgmtItemFactory())
|
||||
|
||||
def test_fill_in_agenda_administrivia(self):
|
||||
roll_call = TelechatAgendaContentFactory(section_id='roll_call')
|
||||
minutes = TelechatAgendaContentFactory(section_id='minutes')
|
||||
action_items = TelechatAgendaContentFactory(section_id='action_items')
|
||||
sections = agenda_sections()
|
||||
fill_in_agenda_administrivia(None, sections) # n.b., date parameter is unused at present
|
||||
self.assertIn(roll_call.text, sections["1.1"]["text"])
|
||||
self.assertIn(minutes.text, sections["1.3"]["text"])
|
||||
self.assertIn(action_items.text, sections["1.4"]["text"])
|
||||
|
||||
def test_fill_in_agenda_docs(self):
|
||||
draft = self.telechat_docs["ietf_draft"]
|
||||
statchg = self.telechat_docs["statchg"]
|
||||
|
@ -337,9 +347,12 @@ class IESGAgendaTests(TestCase):
|
|||
self.assertTrue(r.json())
|
||||
|
||||
def test_agenda(self):
|
||||
action_items = TelechatAgendaContentFactory(section_id='action_items')
|
||||
r = self.client.get(urlreverse("ietf.iesg.views.agenda"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.assertContains(r, action_items.text)
|
||||
|
||||
for k, d in self.telechat_docs.items():
|
||||
if d.type_id == "charter":
|
||||
self.assertContains(r, d.group.name, msg_prefix="%s '%s' not in response" % (k, d.group.name))
|
||||
|
@ -356,6 +369,29 @@ class IESGAgendaTests(TestCase):
|
|||
# Make sure the sort places 6.9 before 6.10
|
||||
self.assertLess(r.content.find(b"6.9"), r.content.find(b"6.10"))
|
||||
|
||||
def test_agenda_restricted_sections(self):
|
||||
r = self.client.get(urlreverse("ietf.iesg.views.agenda"))
|
||||
# not logged in
|
||||
for section_id in ("roll_call", "minutes"):
|
||||
self.assertNotContains(
|
||||
r, urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": section_id})
|
||||
)
|
||||
|
||||
self.client.login(username="plain", password="plain+password")
|
||||
for section_id in ("roll_call", "minutes"):
|
||||
self.assertNotContains(
|
||||
r, urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": section_id})
|
||||
)
|
||||
|
||||
for username in ("ad", "secretary", "iab chair"):
|
||||
self.client.login(username=username, password=f"{username}+password")
|
||||
r = self.client.get(urlreverse("ietf.iesg.views.agenda"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
for section_id in ("roll_call", "minutes"):
|
||||
self.assertContains(
|
||||
r, urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": section_id})
|
||||
)
|
||||
|
||||
def test_agenda_txt(self):
|
||||
r = self.client.get(urlreverse("ietf.iesg.views.agenda_txt"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
@ -549,3 +585,104 @@ class RescheduleOnAgendaTests(TestCase):
|
|||
self.assertEqual(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, d)
|
||||
self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").returning_item)
|
||||
self.assertEqual(draft.docevent_set.count(), events_before + 1)
|
||||
|
||||
|
||||
class TelechatAgendaContentTests(TestCase):
|
||||
def test_telechat_agenda_content_view(self):
|
||||
self.client.login(username="ad", password="ad+password")
|
||||
r = self.client.get(urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": "fake"}))
|
||||
self.assertEqual(r.status_code, 404, "Nonexistent section should 404")
|
||||
for section in TelechatAgendaSectionName.objects.filter(used=True).values_list("slug", flat=True):
|
||||
r = self.client.get(
|
||||
urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": section})
|
||||
)
|
||||
self.assertEqual(r.status_code, 404, "Section with no content should 404")
|
||||
for section in TelechatAgendaSectionName.objects.filter(used=True).values_list("slug", flat=True):
|
||||
content = TelechatAgendaContentFactory(section_id=section).text
|
||||
r = self.client.get(
|
||||
urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": section})
|
||||
)
|
||||
self.assertContains(r, content, status_code=200)
|
||||
self.assertEqual(r.get("Content-Type", None), "text/plain")
|
||||
|
||||
def test_telechat_agenda_content_view_permissions(self):
|
||||
for section in TelechatAgendaSectionName.objects.filter(used=True).values_list("slug", flat=True):
|
||||
TelechatAgendaContentFactory(section_id=section)
|
||||
url = urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": section})
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "plain", url)
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
self.assertEqual(self.client.get(url).status_code, 200)
|
||||
self.client.login(username="iab chair", password="iab chair+password")
|
||||
self.assertEqual(self.client.get(url).status_code, 200)
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
self.assertEqual(self.client.get(url).status_code, 200)
|
||||
|
||||
def test_telechat_agenda_content_edit(self):
|
||||
for section in TelechatAgendaSectionName.objects.filter(used=True):
|
||||
self.assertFalse(TelechatAgendaContent.objects.filter(section=section).exists())
|
||||
url = urlreverse("ietf.iesg.views.telechat_agenda_content_edit", kwargs={"section": section.slug})
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "plain", url, method="get")
|
||||
login_testing_unauthorized(self, "ad", url, method="get")
|
||||
login_testing_unauthorized(self, "iab chair", url, method="get")
|
||||
login_testing_unauthorized(self, "secretary", url, method="get")
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r, str(section), status_code=200)
|
||||
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "plain", url, method="post")
|
||||
login_testing_unauthorized(self, "ad", url, method="post")
|
||||
login_testing_unauthorized(self, "iab chair", url, method="post")
|
||||
login_testing_unauthorized(self, "secretary", url, method="post")
|
||||
r = self.client.post(url, {"text": "This is some content"})
|
||||
self.assertRedirects(r, urlreverse("ietf.iesg.views.telechat_agenda_content_manage"))
|
||||
contents = TelechatAgendaContent.objects.filter(section=section)
|
||||
self.assertEqual(contents.count(), 1)
|
||||
self.assertEqual(contents.first().text, "This is some content")
|
||||
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "plain", url, method="post")
|
||||
login_testing_unauthorized(self, "ad", url, method="post")
|
||||
login_testing_unauthorized(self, "iab chair", url, method="post")
|
||||
login_testing_unauthorized(self, "secretary", url, method="post")
|
||||
r = self.client.post(url, {"text": "This is some different content"})
|
||||
self.assertRedirects(r, urlreverse("ietf.iesg.views.telechat_agenda_content_manage"))
|
||||
contents = TelechatAgendaContent.objects.filter(section=section)
|
||||
self.assertEqual(contents.count(), 1)
|
||||
self.assertEqual(contents.first().text, "This is some different content")
|
||||
|
||||
def test_telechat_agenda_content_manage(self):
|
||||
url = urlreverse("ietf.iesg.views.telechat_agenda_content_manage")
|
||||
login_testing_unauthorized(self, "plain", url)
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
login_testing_unauthorized(self, "iab chair", url)
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
self.assertEqual(TelechatAgendaContent.objects.count(), 0)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
pq = PyQuery(r.content)
|
||||
for section in TelechatAgendaSectionName.objects.filter(used=True):
|
||||
# check that there's a tab even when section is empty
|
||||
nav_button = pq(f"button.nav-link#{section.slug}-tab")
|
||||
self.assertEqual(nav_button.text(), str(section))
|
||||
edit_url = urlreverse("ietf.iesg.views.telechat_agenda_content_edit", kwargs={"section": section.pk})
|
||||
edit_button = pq(f'div#{section.slug} a[href="{edit_url}"]')
|
||||
self.assertEqual(len(edit_button), 1)
|
||||
self.assertIn(f"No {section}", pq(f"div#{section.slug}").text())
|
||||
# and create a section for the next test
|
||||
TelechatAgendaContentFactory(section_id=section.slug)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
pq = PyQuery(r.content)
|
||||
for section in TelechatAgendaSectionName.objects.filter(used=True):
|
||||
# check that there's a tab with the content
|
||||
nav_button = pq(f"button.nav-link#{section.slug}-tab")
|
||||
self.assertEqual(nav_button.text(), str(section))
|
||||
edit_url = urlreverse("ietf.iesg.views.telechat_agenda_content_edit", kwargs={"section": section.pk})
|
||||
edit_button = pq(f'div#{section.slug} a[href="{edit_url}"]')
|
||||
self.assertEqual(len(edit_button), 1)
|
||||
self.assertIn(
|
||||
TelechatAgendaContent.objects.get(section=section).text, pq(f"div#{section.slug}").text()
|
||||
)
|
||||
|
||||
|
|
|
@ -52,6 +52,9 @@ urlpatterns = [
|
|||
|
||||
url(r'^agenda/documents.txt$', views.agenda_documents_txt),
|
||||
url(r'^agenda/documents/$', views.agenda_documents),
|
||||
url(r'^agenda/sections$', views.telechat_agenda_content_manage),
|
||||
url(r'^agenda/section/(?P<section>[a-z_]+)$', views.telechat_agenda_content_view),
|
||||
url(r'^agenda/section/(?P<section>[a-z_]+)/edit$', views.telechat_agenda_content_edit),
|
||||
url(r'^past/documents/$', views.past_documents),
|
||||
url(r'^agenda/telechat-(?:%(date)s-)?docs.tgz' % settings.URL_REGEXPS, views.telechat_docs_tarfile),
|
||||
url(r'^discusses/$', views.discusses),
|
||||
|
|
|
@ -47,8 +47,9 @@ from django import forms
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.sites.models import Site
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.utils.encoding import force_bytes
|
||||
#from django.views.decorators.cache import cache_page
|
||||
#from django.views.decorators.vary import vary_on_cookie
|
||||
|
@ -59,9 +60,10 @@ from ietf.doc.models import Document, State, LastCallDocEvent, ConsensusDocEvent
|
|||
from ietf.doc.utils import update_telechat, augment_events_with_revision
|
||||
from ietf.group.models import GroupMilestone, Role
|
||||
from ietf.iesg.agenda import agenda_data, agenda_sections, fill_in_agenda_docs, get_agenda_date
|
||||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.iesg.models import TelechatDate, TelechatAgendaContent
|
||||
from ietf.iesg.utils import telechat_page_count
|
||||
from ietf.ietfauth.utils import has_role, role_required, user_is_person
|
||||
from ietf.name.models import TelechatAgendaSectionName
|
||||
from ietf.person.models import Person
|
||||
from ietf.meeting.utils import get_activity_stats
|
||||
from ietf.doc.utils_search import fill_in_document_table_attributes, fill_in_telechat_date
|
||||
|
@ -197,8 +199,17 @@ def agenda(request, date=None):
|
|||
data = agenda_data(date)
|
||||
|
||||
if has_role(request.user, ["Area Director", "IAB Chair", "Secretariat"]):
|
||||
data["sections"]["1.1"]["title"] = data["sections"]["1.1"]["title"].replace("Roll call", '<a href="%s">Roll Call</a>' % settings.IESG_ROLL_CALL_URL )
|
||||
data["sections"]["1.3"]["title"] = data["sections"]["1.3"]["title"].replace("minutes", '<a href="%s">Minutes</a>' % settings.IESG_MINUTES_URL)
|
||||
data["sections"]["1.1"]["title"] = data["sections"]["1.1"]["title"].replace(
|
||||
"Roll call",
|
||||
'<a href="{}">Roll Call</a>'.format(
|
||||
urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": "roll_call"})
|
||||
)
|
||||
)
|
||||
data["sections"]["1.3"]["title"] = data["sections"]["1.3"]["title"].replace(
|
||||
"minutes",
|
||||
'<a href="{}">Minutes</a>'.format(
|
||||
urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": "minutes"})
|
||||
))
|
||||
|
||||
request.session['ballot_edit_return_point'] = request.path_info
|
||||
return render(request, "iesg/agenda.html", {
|
||||
|
@ -273,9 +284,7 @@ def agenda_package(request, date=None):
|
|||
"date": data["date"],
|
||||
"sections": sorted(data["sections"].items()),
|
||||
"roll_call": data["sections"]["1.1"]["text"],
|
||||
"roll_call_url": settings.IESG_ROLL_CALL_URL,
|
||||
"minutes": data["sections"]["1.3"]["text"],
|
||||
"minutes_url": settings.IESG_MINUTES_URL,
|
||||
"management_items": [(num, section) for num, section in data["sections"].items() if "6" < num < "7"],
|
||||
"domain": Site.objects.get_current().domain,
|
||||
}, content_type='text/plain')
|
||||
|
@ -561,3 +570,40 @@ def ietf_activity(request):
|
|||
context = get_activity_stats(sdate, edate)
|
||||
context['form'] = form
|
||||
return render(request, "iesg/ietf_activity_report.html", context)
|
||||
|
||||
|
||||
class TelechatAgendaContentForm(forms.Form):
|
||||
text = forms.CharField(max_length=100_000, widget=forms.Textarea, required=False)
|
||||
|
||||
|
||||
@role_required("Secretariat")
|
||||
def telechat_agenda_content_edit(request, section):
|
||||
section = get_object_or_404(TelechatAgendaSectionName, slug=section, used=True)
|
||||
content = TelechatAgendaContent.objects.filter(section=section).first()
|
||||
initial = {"text": content.text} if content else {}
|
||||
if request.method == "POST":
|
||||
form = TelechatAgendaContentForm(data=request.POST, initial=initial)
|
||||
if form.is_valid():
|
||||
TelechatAgendaContent.objects.update_or_create(
|
||||
section=section, defaults={"text": form.cleaned_data["text"]}
|
||||
)
|
||||
return redirect("ietf.iesg.views.telechat_agenda_content_manage")
|
||||
else:
|
||||
form = TelechatAgendaContentForm(initial=initial)
|
||||
return render(request, "iesg/telechat_agenda_content_edit.html", {"section": section, "form": form})
|
||||
|
||||
|
||||
@role_required("Secretariat")
|
||||
def telechat_agenda_content_manage(request):
|
||||
# Fill in any missing instances with empty stand-ins. The edit view will create persistent instances if needed.
|
||||
contents = [
|
||||
TelechatAgendaContent.objects.filter(section=section).first() or TelechatAgendaContent(section=section)
|
||||
for section in TelechatAgendaSectionName.objects.filter(used=True)
|
||||
]
|
||||
return render(request, "iesg/telechat_agenda_content_manage.html", {"contents": contents})
|
||||
|
||||
|
||||
@role_required("Secretariat", "IAB Chair", "Area Director")
|
||||
def telechat_agenda_content_view(request, section):
|
||||
content = get_object_or_404(TelechatAgendaContent, section__slug=section, section__used=True)
|
||||
return HttpResponse(content=content.text, content_type="text/plain")
|
||||
|
|
|
@ -12,7 +12,7 @@ from ietf.name.models import (
|
|||
SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName, TopicAudienceName,
|
||||
DocUrlTagName, ReviewAssignmentStateName, ReviewerQueuePolicyName, TimerangeName,
|
||||
ExtResourceName, ExtResourceTypeName, SlideSubmissionStatusName, ProceedingsMaterialTypeName,
|
||||
AgendaFilterTypeName, SessionPurposeName )
|
||||
AgendaFilterTypeName, SessionPurposeName, TelechatAgendaSectionName )
|
||||
|
||||
|
||||
from ietf.stats.models import CountryAlias
|
||||
|
@ -97,3 +97,4 @@ admin.site.register(DocUrlTagName, NameAdmin)
|
|||
admin.site.register(ExtResourceTypeName, NameAdmin)
|
||||
admin.site.register(SlideSubmissionStatusName, NameAdmin)
|
||||
admin.site.register(SessionPurposeName, NameAdmin)
|
||||
admin.site.register(TelechatAgendaSectionName, NameAdmin)
|
||||
|
|
|
@ -13628,6 +13628,36 @@
|
|||
"model": "name.streamname",
|
||||
"pk": "legacy"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Action items section",
|
||||
"name": "Action Items",
|
||||
"order": 3,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.telechatagendasectionname",
|
||||
"pk": "action_items"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Minutes section",
|
||||
"name": "Minutes",
|
||||
"order": 2,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.telechatagendasectionname",
|
||||
"pk": "minutes"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Roll call section",
|
||||
"name": "Roll Call",
|
||||
"order": 1,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.telechatagendasectionname",
|
||||
"pk": "roll_call"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Friday early afternoon",
|
||||
|
|
27
ietf/name/migrations/0002_telechatagendasectionname.py
Normal file
27
ietf/name/migrations/0002_telechatagendasectionname.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 2.2.28 on 2023-03-10 16:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TelechatAgendaSectionName',
|
||||
fields=[
|
||||
('slug', models.CharField(max_length=32, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('desc', models.TextField(blank=True)),
|
||||
('used', models.BooleanField(default=True)),
|
||||
('order', models.IntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['order', 'name'],
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 2.2.28 on 2023-03-10 16:30
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
TelechatAgendaSectionName = apps.get_model('name', 'TelechatAgendaSectionName')
|
||||
for slug, name, desc, order in (
|
||||
('roll_call', 'Roll Call', 'Roll call section', 1),
|
||||
('minutes', 'Minutes', 'Minutes section', 2),
|
||||
('action_items', 'Action Items', 'Action items section', 3),
|
||||
):
|
||||
TelechatAgendaSectionName.objects.create(slug=slug, name=name, desc=desc, order=order)
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('name', '0002_telechatagendasectionname'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse),
|
||||
]
|
|
@ -148,3 +148,5 @@ class ExtResourceName(NameModel):
|
|||
type = ForeignKey(ExtResourceTypeName)
|
||||
class SlideSubmissionStatusName(NameModel):
|
||||
"Pending, Accepted, Rejected"
|
||||
class TelechatAgendaSectionName(NameModel):
|
||||
"roll_call", "minutes", "action_items"
|
||||
|
|
|
@ -18,7 +18,7 @@ from ietf.name.models import ( AgendaFilterTypeName, AgendaTypeName, BallotPosit
|
|||
ReviewAssignmentStateName, ReviewRequestStateName, ReviewResultName, ReviewTypeName,
|
||||
RoleName, RoomResourceName, SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName,
|
||||
TopicAudienceName, ReviewerQueuePolicyName, TimerangeName, ExtResourceTypeName, ExtResourceName,
|
||||
SlideSubmissionStatusName, ProceedingsMaterialTypeName, SessionPurposeName )
|
||||
SlideSubmissionStatusName, ProceedingsMaterialTypeName, SessionPurposeName, TelechatAgendaSectionName )
|
||||
|
||||
class TimeSlotTypeNameResource(ModelResource):
|
||||
class Meta:
|
||||
|
@ -720,3 +720,20 @@ class SessionPurposeNameResource(ModelResource):
|
|||
"on_agenda": ALL,
|
||||
}
|
||||
api.name.register(SessionPurposeNameResource())
|
||||
|
||||
|
||||
class TelechatAgendaSectionNameResource(ModelResource):
|
||||
class Meta:
|
||||
queryset = TelechatAgendaSectionName.objects.all()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'telechatagendasectionname'
|
||||
ordering = ['slug', ]
|
||||
filtering = {
|
||||
"slug": ALL,
|
||||
"name": ALL,
|
||||
"desc": ALL,
|
||||
"used": ALL,
|
||||
"order": ALL,
|
||||
}
|
||||
api.name.register(TelechatAgendaSectionNameResource())
|
||||
|
|
|
@ -651,11 +651,6 @@ STATUS_CHANGE_PATH = '/a/ietfdata/doc/status-change'
|
|||
AGENDA_PATH = '/a/www/www6s/proceedings/'
|
||||
MEETINGHOST_LOGO_PATH = AGENDA_PATH # put these in the same place as other proceedings files
|
||||
IPR_DOCUMENT_PATH = '/a/www/ietf-ftp/ietf/IPR/'
|
||||
IESG_TASK_FILE = '/a/www/www6/iesg/internal/task.txt'
|
||||
IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt'
|
||||
IESG_ROLL_CALL_URL = 'https://www6.ietf.org/iesg/internal/rollcall.txt'
|
||||
IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt'
|
||||
IESG_MINUTES_URL = 'https://www6.ietf.org/iesg/internal/minutes.txt'
|
||||
IESG_WG_EVALUATION_DIR = "/a/www/www6/iesg/evaluation"
|
||||
# Move drafts to this directory when they expire
|
||||
INTERNET_DRAFT_ARCHIVE_DIR = '/a/ietfdata/doc/draft/collection/draft-archive/'
|
||||
|
|
|
@ -12,6 +12,12 @@
|
|||
<small class="text-muted">{{ date }}</small>
|
||||
</h1>
|
||||
{% include "iesg/nav.html" with active="agenda" %}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<a class="btn btn-outline-primary float-end"
|
||||
href="{% url 'ietf.iesg.views.telechat_agenda_content_manage' %}">
|
||||
Manage administrivia
|
||||
</a>
|
||||
{% endif %}
|
||||
{% for num, section in sections %}
|
||||
{% if num|sectionlevel == 1 %}
|
||||
<h2 class="mt-3" id="sec-{{ num|slugify }}">{{ num }}. {{ section.title|safe }}</h2>
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
Contents:
|
||||
|
||||
1. Roll Call and Dial-In Instructions
|
||||
{{ roll_call_url }}
|
||||
{% url "ietf.iesg.views.telechat_agenda_content_view" section="roll_call" %}
|
||||
2. Agenda
|
||||
{{ settings.IDTRACKER_BASE_URL }}{% url 'ietf.iesg.views.agenda' %}
|
||||
3. Management Item Details
|
||||
{{ settings.IDTRACKER_BASE_URL }}{% url 'ietf.iesg.views.agenda' %}#6
|
||||
4. Previous minutes
|
||||
{{ minutes_url }}
|
||||
{% url "ietf.iesg.views.telechat_agenda_content_view" section="minutes" %}
|
||||
|
||||
------------------------------------------------------------------------
|
||||
1. ROLL CALL AND DIAL-IN INSTRUCTIONS
|
||||
|
|
21
ietf/templates/iesg/telechat_agenda_content_edit.html
Normal file
21
ietf/templates/iesg/telechat_agenda_content_edit.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load django_bootstrap5 %}
|
||||
{% block pagehead %}{{ form.media.css }}{% endblock %}
|
||||
{% block title %}Edit "{{ section }}"{% endblock %}
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>
|
||||
Edit Telechat Agenda Contents
|
||||
<br>
|
||||
<small class="text-muted">{{ section }}</small>
|
||||
</h1>
|
||||
<form class="my-3" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a class="btn btn-secondary float-end" href="{% url 'ietf.iesg.views.telechat_agenda_content_manage' %}">Cancel</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block js %}{{ form.media.js }}{% endblock %}
|
49
ietf/templates/iesg/telechat_agenda_content_manage.html
Normal file
49
ietf/templates/iesg/telechat_agenda_content_manage.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load django_bootstrap5 %}
|
||||
{% block title %}Telechat agenda contents{% endblock %}
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Telechat Agenda Contents</h1>
|
||||
<p>
|
||||
<a href="{% url 'ietf.iesg.views.agenda' %}">Go to IESG agenda...</a>
|
||||
</p>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
{% for item in contents %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if forloop.first %}active{% endif %}"
|
||||
id="{{ item.section.slug }}-tab"
|
||||
type="button"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#{{ item.section.slug}}"
|
||||
role="tab" aria-controls="{{ item.section.slug }}"
|
||||
aria-selected="true">
|
||||
{{ item.section }}
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content card-body">
|
||||
{% for item in contents %}
|
||||
<div class="tab-pane {% if forloop.first %}show active{% endif %}"
|
||||
id="{{ item.section.slug }}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="{{ item.section.slug }}-tab">
|
||||
<a class="btn btn-sm btn-outline-primary float-end"
|
||||
href="{% url 'ietf.iesg.views.telechat_agenda_content_edit' section=item.section.slug %}">
|
||||
Edit
|
||||
</a>
|
||||
{% if item.text %}
|
||||
<pre>{{ item.text }}</pre>
|
||||
{% else %}
|
||||
<div class="text-center text-danger">No {{ item.section }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in a new issue