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:
Jennifer Richards 2023-04-23 19:05:58 -04:00 committed by GitHub
parent 24277f6ff2
commit 372891194e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 456 additions and 38 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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)

View 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')),
],
),
]

View file

@ -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"

View file

@ -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())

View file

@ -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()
)

View file

@ -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),

View file

@ -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")

View file

@ -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)

View file

@ -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",

View 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,
},
),
]

View file

@ -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),
]

View file

@ -148,3 +148,5 @@ class ExtResourceName(NameModel):
type = ForeignKey(ExtResourceTypeName)
class SlideSubmissionStatusName(NameModel):
"Pending, Accepted, Rejected"
class TelechatAgendaSectionName(NameModel):
"roll_call", "minutes", "action_items"

View file

@ -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())

View file

@ -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/'

View file

@ -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>

View file

@ -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

View 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 %}

View 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 %}