Merged in ^/trunk@17782 (just ahead of release 6.130.0)

- Legacy-Id: 17783
This commit is contained in:
Henrik Levkowetz 2020-05-12 15:54:46 +00:00
commit 154876a12a
56 changed files with 1185 additions and 412 deletions

View file

@ -61,4 +61,4 @@ $DTDIR/ietf/manage.py fetch_meeting_attendance --latest 2
$DTDIR/ietf/bin/send-review-reminders
# Purge old request_profiler records
$DTDIR/ietf/manage.py purge_request_profiler_records
$DTDIR/ietf/manage.py purge_request_profiler_records -d2

16
bin/dump-to-names-json Normal file
View file

@ -0,0 +1,16 @@
#!/bin/bash
# This script provides a limited selected dump of database content with the
# purpose of generating a test fixture that provides the test data needed
# by the test suite.
#
# The generated data fixture is sorted and normalized in order to produce
# minimal commit diffs which reflect only actual changes in the fixture data,
# without apparent changes resulting only from ordering changes.
set -x
ietf/manage.py dumpdata --indent 1 doc.State doc.BallotType doc.StateType \
mailtrigger.MailTrigger mailtrigger.Recipient name utils.VersionInfo \
group.GroupFeatures stats.CountryAlias dbtemplate.DBTemplate \
| jq --sort-keys "sort_by(.model, .pk)" \
| jq '[.[] | select(.model!="dbtemplate.dbtemplate" or .pk==354)]' > ietf/name/fixtures/names.json

View file

@ -16,7 +16,5 @@ source $DTDIR/env/bin/activate
logger -p user.info -t cron "Running $DTDIR/bin/every15m"
# Send mail scheduled to go out at certain times
$DTDIR/ietf/bin/send-scheduled-mail all

View file

@ -138,6 +138,7 @@ def check_html_valid(url, response, args):
key = re.sub("/dir/[a-z0-9-]+/", "/dir/foo/", key)
key = re.sub("/draft-[a-z0-9-]+/", "/draft-foo/", key)
key = re.sub("/group/[a-z0-9-]+/", "/group/foo/", key)
key = re.sub("/html/[a-z0-9-]+", "/html/foo/", key)
key = re.sub("/ipr/search/.*", "/ipr/search/", key)
key = re.sub("/meeting/[-0-9a-z]+/agenda/[0-9a-z]+/", "/meeting/nn/agenda/foo/", key)
key = re.sub("/release/[0-9dev.]+/", "/release/n.n.n/", key)

View file

@ -20,3 +20,6 @@ logger -p user.info -t cron "Running $DTDIR/bin/weekly"
$DTDIR/ietf/manage.py send_apikey_usage_emails
# Send notifications about coming expirations
$DTDIR/ietf/bin/notify-expirations

View file

@ -20,19 +20,16 @@ c = BuildmasterConfig = {}
# slave name and password must be configured on the slave.
from buildbot.buildslave import BuildSlave
c['slaves'] = [
BuildSlave("datatracker_lin_py27_1", datatracker_lin_py27_1_pw),
BuildSlave("datatracker_lin_py27_2", datatracker_lin_py27_2_pw),
BuildSlave("datatracker_lin_py27_3", datatracker_lin_py27_3_pw),
BuildSlave("datatracker_osx_py27_4", datatracker_osx_py27_4_pw),
BuildSlave("datatracker_lin_py27_5", datatracker_lin_py27_5_pw),
BuildSlave("datatracker_lin_py27_6", datatracker_lin_py27_6_pw),
#
BuildSlave("datatracker_lin_py36_1", datatracker_lin_py36_1_pw),
BuildSlave("datatracker_lin_py36_2", datatracker_lin_py36_2_pw),
BuildSlave("datatracker_lin_py36_3", datatracker_lin_py36_3_pw),
BuildSlave("datatracker_lin_py36_4", datatracker_lin_py36_4_pw),
BuildSlave("datatracker_lin_py36_5", datatracker_lin_py36_5_pw),
BuildSlave("datatracker_lin_py36_6", datatracker_lin_py36_6_pw),
BuildSlave("dunkelfelder_lin_py36_1", dunkelfelder_lin_py36_1_pw),
BuildSlave("dunkelfelder_lin_py36_2", dunkelfelder_lin_py36_2_pw),
BuildSlave("dunkelfelder_lin_py36_3", dunkelfelder_lin_py36_3_pw),
BuildSlave("dunkelfelder_lin_py36_4", dunkelfelder_lin_py36_4_pw),
BuildSlave("dornfelder_lin_py36_1", dornfelder_lin_py36_1_pw),
BuildSlave("dornfelder_lin_py36_2", dornfelder_lin_py36_2_pw),
BuildSlave("dornfelder_lin_py36_3", dornfelder_lin_py36_3_pw),
BuildSlave("dornfelder_lin_py36_4", dornfelder_lin_py36_4_pw),
]
# 'protocols' contains information about protocols which master will use for
@ -93,7 +90,7 @@ c['schedulers'] = [
# Periodic Schedulers
Nightly(name="lin_test_old_libs", hour=16, minute=12, branch="trunk", builderNames=["Verify Minimum Libs"],),
Nightly(name="lin_test_libs", hour=16, minute=42, branch="trunk", builderNames=["Verify Latest Libs"],),
Nightly(name="crawler", hour=9, minute=00, branch="trunk", onlyIfChanged=True, builderNames=["Test-Crawler"],),
Nightly(name="crawler", hour=23, minute=00, branch="trunk", onlyIfChanged=True, builderNames=["Test-Crawler"],),
# Force schedulers
ForceScheduler(name="force_pyflakes", builderNames=["Check PyFlakes"]),
@ -311,6 +308,7 @@ c['builders'] = []
# -*- section Builder_Run_pyflakes -*-
factory = BuildFactory()
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(SVN(
username='buildbot@tools.ietf.org',
descriptionDone="svn update",
@ -320,7 +318,13 @@ factory.addStep(SVN(
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
))
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(ShellCommand(
descriptionDone="install requirements",
workdir=Interpolate('build/%(src::branch)s'),
haltOnFailure=True,
usePTY=False,
command=["pip", "install", "-r", "requirements.txt"],
))
factory.addStep(ShellCommand(
descriptionDone="seting up settings_local.py",
workdir=Interpolate('build/%(src::branch)s'),
@ -338,21 +342,23 @@ factory.addStep(PyFlakes(
factory.addStep(ShellCommand(
descriptionDone="mark as passed",
workdir=Interpolate('build/%(src::branch)s'),
flunkOnFailure=False,
usePTY=False,
command=["svn", "--username=buildbot@tools.ietf.org", "--non-interactive",
"propset", "--revprop", "-r", Property('got_revision'), "test:pyflakes", "passed" ],
))
c['builders'].append(BuilderConfig(name="Check PyFlakes", factory=factory, category="1. trunk",
slavenames=["datatracker_lin_py36_1", "datatracker_lin_py36_4", ]))
slavenames=["dunkelfelder_lin_py36_1", "dornfelder_lin_py36_1", ]))
c['builders'].append(BuilderConfig(name="[branch] Check PyFlakes", factory=factory, category="2. branch",
slavenames=["datatracker_lin_py36_2", ]))
slavenames=["dunkelfelder_lin_py36_2", "dornfelder_lin_py36_2", ]))
c['builders'].append(BuilderConfig(name="[personal] Check PyFlakes", factory=factory, category="3. personal",
slavenames=["datatracker_lin_py36_3", ]))
slavenames=["dunkelfelder_lin_py36_2",]))
# -*- section Builder_TestSuite -*-
factory = BuildFactory()
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(SVN(
username='buildbot@tools.ietf.org',
descriptionDone="svn update",
@ -362,7 +368,7 @@ factory.addStep(SVN(
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s')))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s'), usePTY=False))
factory.addStep(ShellCommand(
descriptionDone="remove tmp-* dirs",
workdir=Interpolate('build/%(src::branch)s'),
@ -377,7 +383,6 @@ factory.addStep(ShellCommand(
usePTY=False,
command=["pip", "install", "-r", "requirements.txt"],
))
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(ShellCommand(
descriptionDone="copy settings_local.py",
workdir=Interpolate('build/%(src::branch)s'),
@ -403,22 +408,32 @@ factory.addStep(UnitTest(
factory.addStep(ShellCommand(
descriptionDone="mark as passed",
workdir=Interpolate('build/%(src::branch)s'),
flunkOnFailure=False,
usePTY=False,
command=["svn", "--username=buildbot@tools.ietf.org", "--non-interactive",
"propset", "--revprop", "-r", Property('got_revision'), "test:unittest", "passed" ],
))
c['builders'].append(BuilderConfig(name="Test Suite", factory=factory, category="1. trunk",
slavenames=["datatracker_lin_py36_1", "datatracker_lin_py36_4", ]))
slavenames=["dunkelfelder_lin_py36_1", "dornfelder_lin_py36_1", ]))
c['builders'].append(BuilderConfig(name="[branch] Test Suite", factory=factory, category="2. branch",
slavenames=["datatracker_lin_py36_2", ]))
slavenames=["dunkelfelder_lin_py36_2", "dornfelder_lin_py36_2", ]))
c['builders'].append(BuilderConfig(name="[personal] Test Suite", factory=factory, category="3. personal",
slavenames=["datatracker_lin_py36_3", ]))
slavenames=["dunkelfelder_lin_py36_2", "dornfelder_lin_py36_2", ]))
# -*- section Builder_TestCrawler -*-
factory = BuildFactory()
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(ShellCommand(
descriptionDone="update database",
workdir=Interpolate('build/%(src::branch)s'),
haltOnFailure=True,
usePTY=False,
timeout=3600, # 1 hour
command=["docker/updatedb", "-q"],
))
factory.addStep(SVN(
username='buildbot@tools.ietf.org',
descriptionDone="svn update",
@ -428,7 +443,7 @@ factory.addStep(SVN(
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s')))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s'), usePTY=False))
factory.addStep(ShellCommand(
descriptionDone="install requirements",
workdir=Interpolate('build/%(src::branch)s'),
@ -436,7 +451,6 @@ factory.addStep(ShellCommand(
usePTY=False,
command=["pip", "install", "-r", "requirements.txt"],
))
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(ShellCommand(
descriptionDone="copy settings_local.py",
workdir=Interpolate('build/%(src::branch)s'),
@ -451,6 +465,14 @@ factory.addStep(ShellCommand(
usePTY=False,
command=["ietf/manage.py", "migrate"],
))
# This will not only do a prelimnary sanity check, but also patch libs as needed:
factory.addStep(ShellCommand(
descriptionDone="run django checks",
workdir=Interpolate('build/%(src::branch)s'),
haltOnFailure=True,
usePTY=False,
command=["ietf/manage.py", "check"],
))
factory.addStep(TestCrawlerShellCommand(
workdir=Interpolate('build/%(src::branch)s'),
haltOnFailure=True,
@ -461,13 +483,14 @@ factory.addStep(TestCrawlerShellCommand(
factory.addStep(ShellCommand(
descriptionDone="mark as passed",
workdir=Interpolate('build/%(src::branch)s'),
flunkOnFailure=False,
usePTY=False,
command=["svn", "--username=buildbot@tools.ietf.org", "--non-interactive",
"propset", "--revprop", "-r", Property('got_revision'), "test:crawler", "passed" ],
))
c['builders'].append(BuilderConfig(name="Test-Crawler", factory=factory, category="1. trunk",
slavenames=["datatracker_lin_py36_6", ]))
slavenames=["dunkelfelder_lin_py36_4", ]))
# -*- section Builder_Verify_Old_Libs -*-
@ -479,6 +502,7 @@ c['builders'].append(BuilderConfig(name="Test-Crawler", factory=factory, categor
# dependencies.
factory = BuildFactory()
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(ShellCommand(
descriptionDone="remove tweaked requirements",
workdir=Interpolate('build/%(src::branch)s'),
@ -497,7 +521,7 @@ factory.addStep(SVN(
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s')))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s'), usePTY=False))
factory.addStep(ShellCommand(
descriptionDone="edit requirements",
workdir=Interpolate('build/%(src::branch)s'),
@ -512,7 +536,6 @@ factory.addStep(ShellCommand(
usePTY=False,
command=["pip", "install", "--upgrade", "-r", "requirements.txt"],
))
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(ShellCommand(
descriptionDone="seting up settings_local.py",
workdir=Interpolate('build/%(src::branch)s'),
@ -542,10 +565,10 @@ factory.addStep(UnitTest(
command=["ietf/manage.py", "test", "--settings=settings_sqlitetest", "--verbosity=2", ],
))
c['builders'].append(BuilderConfig(name="Verify Minimum Libs", factory=factory, category="1. trunk",
slavenames=["datatracker_lin_py36_5", ]))
slavenames=["dornfelder_lin_py36_3", ]))
# -*- section Builder_Dependencies -*-
# -*- section Verify_Latest_Libs -*-
# This build runs pip install --upgrade, to make sure that we install the latest version of all
# dependencies, in order to get an indication if/when an incompatibility turns up with a new
@ -554,6 +577,7 @@ c['builders'].append(BuilderConfig(name="Verify Minimum Libs", factory=factory,
# dependencies.
factory = BuildFactory()
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(SVN(
username='buildbot@tools.ietf.org',
descriptionDone="svn update",
@ -564,7 +588,7 @@ factory.addStep(SVN(
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s')))
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s'), usePTY=False))
factory.addStep(ShellCommand(
descriptionDone="install/upgrade requirements",
workdir=Interpolate('build/%(src::branch)s'),
@ -572,7 +596,6 @@ factory.addStep(ShellCommand(
usePTY=False,
command=["pip", "install", "--upgrade", "-r", "requirements.txt"],
))
factory.addStep(SetPropertiesFromEnv(variables=['HOME',]))
factory.addStep(ShellCommand(
descriptionDone="seting up settings_local.py",
workdir=Interpolate('build/%(src::branch)s'),
@ -603,7 +626,7 @@ factory.addStep(UnitTest(
))
c['builders'].append(BuilderConfig(name="Verify Latest Libs", factory=factory, category="1. trunk",
slavenames=["datatracker_lin_py36_5", ]))
slavenames=["dornfelder_lin_py36_3", ]))
####### STATUS TARGETS
@ -673,7 +696,7 @@ c['status'].append(mail.MailNotifier(
# installation's html.WebStatus home page (linked to the
# 'titleURL') and is embedded in the title of the waterfall HTML page.
c['title'] = "IETF Datatracker"
c['title'] = "Buildbot: IETF Datatracker"
c['titleURL'] = "https://datatracker.ietf.org/"
# the 'buildbotURL' string should point to the location where the buildbot's

View file

@ -158,7 +158,7 @@ class DocumentInfo(models.Model):
else:
self._cached_base_name = "%s-%s.txt" % (self.name, self.rev)
elif self.type_id in ["slides", "agenda", "minutes", "bluesheets", ] and self.meeting_related():
self._cached_base_name = "%s-%s.txt" % self.canonical_name()
self._cached_base_name = "%s-%s.txt" % (self.canonical_name(), self.rev)
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

View file

@ -256,7 +256,9 @@ def document_main(request, name, rev=None):
if "pdf" not in found_types:
file_urls.append(("pdf", settings.TOOLS_ID_PDF_URL + doc.name + "-" + doc.rev + ".pdf"))
file_urls.append(("htmlized", settings.TOOLS_ID_HTML_URL + doc.name + "-" + doc.rev))
#file_urls.append(("htmlized", settings.TOOLS_ID_HTML_URL + doc.name + "-" + doc.rev))
file_urls.append(("htmlized (tools)", settings.TOOLS_ID_HTML_URL + doc.name + "-" + doc.rev))
file_urls.append(("htmlized", urlreverse('ietf.doc.views_doc.document_html', kwargs=dict(name=doc.name, rev=doc.rev))))
# latest revision
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
@ -683,7 +685,7 @@ def document_html(request, name, rev=None):
doc = docs.get()
if not os.path.exists(doc.get_file_name()):
raise Http404("Document not found: %s" % doc.get_base_name())
raise Http404("File not found: %s" % doc.get_file_name())
top = render_document_top(request, doc, "status", name)
if not rev and not name.startswith('rfc'):

View file

@ -18,6 +18,15 @@ class GroupFactory(factory.DjangoModelFactory):
list_email = factory.LazyAttribute(lambda a: '%s@ietf.org'% a.acronym)
uses_milestone_dates = True
@factory.lazy_attribute
def parent(self):
if self.type_id in ['wg','ag']:
return GroupFactory(type_id='area')
elif self.type_id in ['rg']:
return GroupFactory(acronym='irtf', type_id='irtf')
else:
return None
class ReviewTeamFactory(GroupFactory):
type_id = 'review'

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-04 13:10
from __future__ import unicode_literals
from django.db import migrations
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('group', '0023_use_milestone_dates_default_to_true'),
]
operations = [
migrations.AddField(
model_name='groupfeatures',
name='groupman_authroles',
field=jsonfield.fields.JSONField(default=['Secretariat'], max_length=128),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='groupman_authroles',
field=jsonfield.fields.JSONField(default=['Secretariat'], max_length=128),
),
]

View file

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-01 12:54
from __future__ import unicode_literals
from django.db import migrations
authroles_map = {
'adhoc': ['Secretariat'],
'admin': ['Secretariat'],
'ag': ['Secretariat', 'Area Director'],
'area': ['Secretariat'],
'dir': ['Secretariat'],
'iab': ['Secretariat'],
'iana': ['Secretariat'],
'iesg': ['Secretariat'],
'ietf': ['Secretariat'],
'individ': [],
'irtf': ['Secretariat'],
'ise': ['Secretariat'],
'isoc': ['Secretariat'],
'nomcom': ['Secretariat'],
'program': ['Secretariat', 'IAB'],
'review': ['Secretariat'],
'rfcedtyp': ['Secretariat'],
'rg': ['Secretariat', 'IRTF Chair'],
'sdo': ['Secretariat'],
'team': ['Secretariat'],
'wg': ['Secretariat', 'Area Director'],
}
def forward(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
for type_id, authroles in authroles_map.items():
GroupFeatures.objects.filter(type_id=type_id).update(groupman_authroles=authroles)
def reverse(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('group', '0024_add_groupman_authroles'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-01 12:54
from __future__ import unicode_literals
from django.db import migrations
def forward(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
program = GroupFeatures.objects.get(type_id='program')
program.has_meetings = True
program.matman_roles = ['lead', 'chair', 'secr']
program.docman_roles = ['lead', 'chair', 'secr']
program.groupman_roles = ['lead', 'chair', 'secr']
program.role_order = ['lead', 'chair', 'secr']
program.save()
def reverse(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
program = GroupFeatures.objects.get(type_id='program')
program.has_meetings = False
program.matman_roles = ['lead', 'secr']
program.docman_roles = ['lead', 'secr']
program.groupman_roles = ['lead', 'secr']
program.role_order = ['lead', 'secr']
program.save()
class Migration(migrations.Migration):
dependencies = [
('group', '0025_populate_groupman_authroles'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-08 09:02
from __future__ import unicode_literals
from django.db import migrations
def forward(apps, schema_editor):
Group = apps.get_model('group','Group')
iab = Group.objects.get(acronym='iab')
Group.objects.filter(type_id='program').update(parent=iab)
def reverse(apps, schema_editor):
pass # No point in removing the parents
class Migration(migrations.Migration):
dependencies = [
('group', '0026_programs_meet'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -65,9 +65,6 @@ class GroupInfo(models.Model):
kwargs["group_type"] = self.type_id
return urlreverse(self.features.about_page, kwargs=kwargs)
def interim_approval_roles(self):
return list(set([ role for role in self.parent.role_set.filter(name__in=['ad', 'chair']) ]))
def is_bof(self):
return self.state_id in ["bof", "bof-conc"]
@ -238,6 +235,7 @@ class GroupFeatures(models.Model):
admin_roles = jsonfield.JSONField(max_length=64, blank=False, default=["chair"]) # Trac Admin
docman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair","delegate","secr"])
groupman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair",])
groupman_authroles = jsonfield.JSONField(max_length=128, blank=False, default=["Secretariat",])
matman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair","delegate","secr"])
role_order = jsonfield.JSONField(max_length=128, blank=False, default=["chair","secr","member"],
help_text="The order in which roles are shown, for instance on photo pages. Enter valid JSON.")

View file

@ -264,7 +264,7 @@ class GroupPagesTests(TestCase):
can_edit = {
'wg' : ['secretary','ad'],
'rg' : ['secretary','irtf-chair'],
'ag' : ['secretary', ],
'ag' : ['secretary', 'ad' ],
'team' : ['secretary',], # The code currently doesn't let ads edit teams or directorates. Maybe it should.
'dir' : ['secretary',],
'review' : ['secretary',],

View file

@ -15,7 +15,7 @@ import debug # pyflakes:ignore
from ietf.community.models import CommunityList, SearchRule
from ietf.community.utils import reset_name_contains_index_for_rule, can_manage_community_list
from ietf.doc.models import Document, State
from ietf.group.models import Group, RoleHistory, Role
from ietf.group.models import Group, RoleHistory, Role, GroupFeatures
from ietf.ietfauth.utils import has_role
from ietf.name.models import GroupTypeName
from ietf.person.models import Email
@ -105,6 +105,7 @@ def save_milestone_in_history(milestone):
return h
# TODO: rework this using features.groupman_authroles
def can_manage_group_type(user, group, type_id=None):
if not user.is_authenticated:
return False
@ -125,8 +126,11 @@ def can_manage_group_type(user, group, type_id=None):
return has_role(user, ('Secretariat'))
def can_manage_group(user, group):
if can_manage_group_type(user, group):
return True
if not user.is_authenticated:
return False
for authrole in group.features.groupman_authroles:
if has_role(user, authrole):
return True
return group.has_role(user, group.features.groupman_roles)
def milestone_reviewer_for_group_type(group_type):
@ -141,6 +145,18 @@ def can_manage_materials(user, group):
def can_manage_session_materials(user, group, session):
return has_role(user, 'Secretariat') or (group.has_role(user, group.features.matman_roles) and not session.is_material_submission_cutoff())
# Maybe this should be cached...
def can_manage_some_groups(user):
if not user.is_authenticated:
return False
for gf in GroupFeatures.objects.all():
for authrole in gf.groupman_authroles:
if has_role(user, authrole):
return True
if Role.objects.filter(name__in=gf.groupman_roles, group__type_id=gf.type_id, person__user=user).exists():
return True
return False
def can_provide_status_update(user, group):
if not group.features.acts_like_wg:
return False

View file

@ -70,6 +70,9 @@ def has_role(user, role_names, *args, **kwargs):
"RG Secretary": Q(person=person,name="secr", group__type="rg", group__state__in=["active","proposed"]),
"AG Secretary": Q(person=person,name="secr", group__type="ag", group__state__in=["active"]),
"Team Chair": Q(person=person,name="chair", group__type="team", group__state="active"),
"Program Lead": Q(person=person,name="lead", group__type="program", group__state="active"),
"Program Secretary": Q(person=person,name="secr", group__type="program", group__state="active"),
"Program Chair": Q(person=person,name="chair", group__type="program", group__state="active"),
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),

View file

@ -166,6 +166,8 @@ class Recipient(models.Model):
addrs.extend(group.role_set.filter(name='ad').values_list('email__address',flat=True))
if group.type_id=='rg':
addrs.extend(Recipient.objects.get(slug='stream_managers').gather(**{'streams':['irtf']}))
elif group.type_id=='program':
addrs.extend(Recipient.objects.get(slug='iab').gather(**{}))
return addrs
def gather_group_secretaries(self, **kwargs):

View file

@ -15,7 +15,7 @@ from django.forms import BaseInlineFormSet
import debug # pyflakes:ignore
from ietf.doc.models import Document, DocAlias, State, NewRevisionDocEvent
from ietf.group.models import Group
from ietf.group.models import Group, GroupFeatures
from ietf.ietfauth.utils import has_role
from ietf.meeting.models import Session, Meeting, Schedule, countries, timezones
from ietf.meeting.helpers import get_next_interim_number, make_materials_directories
@ -100,8 +100,7 @@ class InterimSessionInlineFormSet(BaseInlineFormSet):
return # formset doesn't have cleaned_data
class InterimMeetingModelForm(forms.ModelForm):
# TODO: Should area groups get to schedule Interims?
group = GroupModelChoiceField(queryset=Group.objects.filter(type__in=('wg', 'rg', 'ag'), state__in=('active', 'proposed', 'bof')).order_by('acronym'), required=False)
group = GroupModelChoiceField(queryset=Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof')).order_by('acronym'), required=False)
in_person = forms.BooleanField(required=False)
meeting_type = forms.ChoiceField(choices=(
("single", "Single"),
@ -156,13 +155,15 @@ class InterimMeetingModelForm(forms.ModelForm):
return # don't reduce group options
q_objects = Q()
if has_role(self.user, "Area Director"):
q_objects.add(Q(type="wg", state__in=("active", "proposed", "bof")), Q.OR)
q_objects.add(Q(type__in=["wg", "ag"], state__in=("active", "proposed", "bof")), Q.OR)
if has_role(self.user, "IRTF Chair"):
q_objects.add(Q(type="rg", state__in=("active", "proposed")), Q.OR)
if has_role(self.user, "WG Chair"):
q_objects.add(Q(type="wg", state__in=("active", "proposed", "bof"), role__person=self.person, role__name="chair"), Q.OR)
if has_role(self.user, "RG Chair"):
q_objects.add(Q(type="rg", state__in=("active", "proposed"), role__person=self.person, role__name="chair"), Q.OR)
if has_role(self.user, "Program Lead") or has_role(self.user, "Program Chair"):
q_objects.add(Q(type="program", state__in=("active", "proposed"), role__person=self.person, role__name__in=["chair", "lead"]), Q.OR)
queryset = Group.objects.filter(q_objects).distinct().order_by('acronym')
self.fields['group'].queryset = queryset

View file

@ -21,6 +21,7 @@ import debug # pyflakes:ignore
from ietf.doc.models import Document
from ietf.group.models import Group
from ietf.group.utils import can_manage_some_groups, can_manage_group
from ietf.ietfauth.utils import has_role, user_is_person
from ietf.liaisons.utils import get_person_for_user
from ietf.mailtrigger.utils import gather_address_lists
@ -324,11 +325,14 @@ def can_approve_interim_request(meeting, user):
if not session:
return False
group = session.group
if group.type.slug == 'wg':
if group.type.slug in ['wg','ag']:
if group.parent.role_set.filter(name='ad', person=person) or group.role_set.filter(name='ad', person=person):
return True
if group.type.slug == 'rg' and group.parent.role_set.filter(name='chair', person=person):
return True
if group.type.slug == 'program':
if person.role_set.filter(group__acronym='iab', name='member'):
return True
return False
@ -336,14 +340,13 @@ def can_edit_interim_request(meeting, user):
'''Returns True if the user can edit the interim meeting request'''
if meeting.type.slug != 'interim':
return False
if has_role(user, 'Secretariat'):
if has_role(user, 'Secretariat'): # Consider removing - can_manage_group should handle this
return True
person = get_person_for_user(user)
session = meeting.session_set.first()
if not session:
return False
group = session.group
if group.role_set.filter(name='chair', person=person):
if can_manage_group(user, group):
return True
elif can_approve_interim_request(meeting, user):
return True
@ -352,29 +355,17 @@ def can_edit_interim_request(meeting, user):
def can_request_interim_meeting(user):
if has_role(user, ('Secretariat', 'Area Director', 'WG Chair', 'IRTF Chair', 'RG Chair')):
return True
return False
return can_manage_some_groups(user)
def can_view_interim_request(meeting, user):
'''Returns True if the user can see the pending interim request in the pending interim view'''
if meeting.type.slug != 'interim':
return False
if has_role(user, 'Secretariat'):
return True
person = get_person_for_user(user)
session = meeting.session_set.first()
if not session:
return False
group = session.group
if has_role(user, 'Area Director') and group.type.slug == 'wg':
return True
if has_role(user, 'IRTF Chair') and group.type.slug == 'rg':
return True
if group.role_set.filter(name='chair', person=person):
return True
return False
return can_manage_group(user, group)
def create_interim_meeting(group, date, city='', country='', timezone='UTC',
@ -512,11 +503,17 @@ def send_interim_approval_request(meetings):
else:
is_series = False
approver_set = set()
for role in group.interim_approval_roles():
approver = "%s of the %s" % ( role.name.name, role.group.name)
approver_set.add(approver)
for authrole in group.features.groupman_authroles: # NOTE: This makes an assumption that the authroles are exactly the set of approvers
approver_set.add(authrole)
approvers = list(approver_set)
context = locals() # TODO Unnecessarily complex, context needs to only contain what the template needs
context = {
'group': group,
'is_series': is_series,
'meetings': meetings,
'approvers': approvers,
'requester': requester,
'approval_urls': approval_urls,
}
send_mail(None,
to_email,
from_email,

View file

@ -793,7 +793,7 @@ class SchedTimeSessAssignment(models.Model):
if not self.timeslot:
components.append("unknown")
if not self.session or not (getattr(self.session, "historic_group") or self.session.group):
if not self.session or not (getattr(self.session, "historic_group", None) or self.session.group):
components.append("unknown")
else:
components.append(self.timeslot.time.strftime("%Y-%m-%d-%a-%H%M"))

View file

@ -26,7 +26,8 @@ from django.db.models import F
import debug # pyflakes:ignore
from ietf.doc.models import Document
from ietf.group.models import Group, Role
from ietf.group.models import Group, Role, GroupFeatures
from ietf.group.utils import can_manage_group
from ietf.person.models import Person
from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request
from ietf.meeting.helpers import send_interim_approval_request
@ -37,7 +38,7 @@ from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
from ietf.meeting.utils import finalize, condition_slide_order
from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.meeting.views import session_draft_list
from ietf.name.models import SessionStatusName, ImportantDateName
from ietf.name.models import SessionStatusName, ImportantDateName, RoleName
from ietf.utils.decorators import skip_coverage
from ietf.utils.mail import outbox, empty_outbox, get_payload
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
@ -1242,6 +1243,12 @@ class SessionDetailsTests(TestCase):
self.assertTrue(all([x in unicontent(r) for x in ('slides','agenda','minutes','draft')]))
self.assertNotContains(r, 'deleted')
q = PyQuery(r.content)
self.assertTrue(q('h2#session_%s div#session-buttons-%s' % (session.id, session.id)),
'Session detail page does not contain session tool buttons')
self.assertFalse(q('h2#session_%s div#session-buttons-%s span.fa-arrows-alt' % (session.id, session.id)),
'The session detail page is incorrectly showing the "Show meeting materials" button')
def test_session_details_past_interim(self):
group = GroupFactory.create(type_id='wg',state_id='active')
chair = RoleFactory(name_id='chair',group=group)
@ -1491,8 +1498,7 @@ class InterimTests(TestCase):
self.assertContains(r, 'IETF 72')
# cancelled session
q = PyQuery(r.content)
# self.assertIn('CANCELLED', q('[id*="-ames"]').text())
self.assertIn('CANCELLED', q('tr>td>a>span').text())
self.assertIn('CANCELLED', q('tr>td.text-right>span').text())
self.check_interim_tabs(url)
def test_upcoming_ical(self):
@ -1555,7 +1561,8 @@ class InterimTests(TestCase):
r = self.client.get("/meeting/interim/request/")
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(Group.objects.filter(type__in=('wg', 'rg', 'ag'), state__in=('active', 'proposed')).count(),
Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof'))
self.assertEqual(Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof')).count(),
len(q("#id_group option")) - 1) # -1 for options placeholder
self.client.logout()
@ -1939,6 +1946,28 @@ class InterimTests(TestCase):
user = User.objects.get(username='ameschairman')
self.assertFalse(can_view_interim_request(meeting=meeting,user=user))
def test_can_manage_group(self):
make_meeting_test_data()
# unprivileged user
user = User.objects.get(username='plain')
group = Group.objects.get(acronym='mars')
self.assertFalse(can_manage_group(user=user,group=group))
# Secretariat
user = User.objects.get(username='secretary')
self.assertTrue(can_manage_group(user=user,group=group))
# related AD
user = User.objects.get(username='ad')
self.assertTrue(can_manage_group(user=user,group=group))
# other AD
user = User.objects.get(username='ops-ad')
self.assertTrue(can_manage_group(user=user,group=group))
# WG Chair
user = User.objects.get(username='marschairman')
self.assertTrue(can_manage_group(user=user,group=group))
# Other WG Chair
user = User.objects.get(username='ameschairman')
self.assertFalse(can_manage_group(user=user,group=group))
def test_interim_request_details(self):
make_meeting_test_data()
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
@ -1983,13 +2012,6 @@ class InterimTests(TestCase):
make_meeting_test_data()
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
# ensure no cancel button for unauthorized user
self.client.login(username="ameschairman", password="ameschairman+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q("a.btn:contains('Cancel')")), 0)
# ensure cancel button for authorized user
self.client.login(username="marschairman", password="marschairman+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
@ -2793,3 +2815,175 @@ class SessionTests(TestCase):
})
self.assertEqual(r.status_code,302)
self.assertEqual(len(outbox),1)
class HasMeetingsTests(TestCase):
def setUp(self):
self.materials_dir = self.tempdir('materials')
#
self.saved_agenda_path = settings.AGENDA_PATH
#
settings.AGENDA_PATH = self.materials_dir
def tearDown(self):
shutil.rmtree(self.materials_dir)
#
settings.AGENDA_PATH = self.saved_agenda_path
def do_request_interim(self, url, group, user, meeting_count):
login_testing_unauthorized(self,user.username, url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('#id_group option[value="%d"]'%group.pk))
date = datetime.date.today() + datetime.timedelta(days=30+meeting_count)
time = datetime.datetime.now().time().replace(microsecond=0,second=0)
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
meeting_count = Meeting.objects.filter(number__contains='-%s-'%group.acronym, date__year=date.year).count()
next_num = "%02d" % (meeting_count+1)
data = {'group':group.pk,
'meeting_type':'single',
'city':'',
'country':'',
'time_zone':'UTC',
'session_set-0-date':date.strftime("%Y-%m-%d"),
'session_set-0-time':time.strftime('%H:%M'),
'session_set-0-requested_duration':'03:00:00',
'session_set-0-remote_instructions':remote_instructions,
'session_set-0-agenda':agenda,
'session_set-0-agenda_note':agenda_note,
'session_set-TOTAL_FORMS':1,
'session_set-INITIAL_FORMS':0,
'session_set-MIN_NUM_FORMS':0,
'session_set-MAX_NUM_FORMS':1000}
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming'))
meeting = Meeting.objects.order_by('id').last()
self.assertEqual(meeting.type_id,'interim')
self.assertEqual(meeting.date,date)
self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year, group.acronym, next_num))
self.client.logout()
def create_role_for_authrole(self, authrole):
role = None
if authrole == 'Secretariat':
role = RoleFactory.create(group__acronym='secretariat',name_id='secr')
elif authrole == 'Area Director':
role = RoleFactory.create(name_id='ad', group__type_id='area')
elif authrole == 'IAB':
role = RoleFactory.create(name_id='member', group__acronym='iab')
elif authrole == 'IRTF Chair':
role = RoleFactory.create(name_id='chair', group__acronym='irtf')
if role is None:
self.assertIsNone("Can't test authrole:"+authrole)
self.assertNotEqual(role, None)
return role
def test_can_request_interim(self):
url = urlreverse('ietf.meeting.views.interim_request')
for gf in GroupFeatures.objects.filter(has_meetings=True):
meeting_count = 0
for role in gf.groupman_roles:
role = RoleFactory(group__type_id=gf.type_id, name_id=role)
self.do_request_interim(url, role.group, role.person.user, meeting_count)
for authrole in gf.groupman_authroles:
group = GroupFactory(type_id=gf.type_id)
role = self.create_role_for_authrole(authrole)
self.do_request_interim(url, group, role.person.user, 0)
def test_cannot_request_interim(self):
url = urlreverse('ietf.meeting.views.interim_request')
self.client.login(username='secretary', password='secretary+password')
nomeetings = []
for gf in GroupFeatures.objects.exclude(has_meetings=True):
nomeetings.append(GroupFactory(type_id=gf.type_id))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
for group in nomeetings:
self.assertFalse(q('#id_group option[value="%d"]'%group.pk))
self.client.logout()
all_role_names = set(RoleName.objects.values_list('slug',flat=True))
for gf in GroupFeatures.objects.filter(has_meetings=True):
for role_name in all_role_names - set(gf.groupman_roles):
role = RoleFactory(group__type_id=gf.type_id,name_id=role_name)
self.client.login(username=role.person.user.username, password=role.person.user.username+'+password')
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
self.client.logout()
def test_appears_on_upcoming(self):
url = urlreverse('ietf.meeting.views.upcoming')
for gf in GroupFeatures.objects.filter(has_meetings=True):
session = SessionFactory(
group__type_id = gf.type_id,
meeting__type_id='interim',
meeting__date = datetime.datetime.today()+datetime.timedelta(days=30),
status_id='sched',
)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())
def test_appears_on_pending(self):
url = urlreverse('ietf.meeting.views.interim_pending')
for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id)
meeting_date = datetime.datetime.today() + datetime.timedelta(days=30)
session = SessionFactory(
group=group,
meeting__type_id='interim',
meeting__date = meeting_date,
meeting__number = 'interim-%d-%s-00'%(meeting_date.year,group.acronym),
status_id='apprw',
)
for role_name in gf.groupman_roles:
role = RoleFactory(group=group, name_id=role_name)
self.client.login(username=role.person.user.username, password=role.person.user.username+'+password')
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())
self.client.logout()
for authrole in gf.groupman_authroles:
role = self.create_role_for_authrole(authrole)
self.client.login(username=role.person.user.username, password=role.person.user.username+'+password')
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())
self.client.logout()
def test_appears_on_announce(self):
url = urlreverse('ietf.meeting.views.interim_announce')
login_testing_unauthorized(self,"secretary",url)
sessions=[]
for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id)
meeting_date = datetime.datetime.today() + datetime.timedelta(days=30)
session = SessionFactory(
group=group,
meeting__type_id='interim',
meeting__date = meeting_date,
meeting__number = 'interim-%d-%s-00'%(meeting_date.year,group.acronym),
status_id='scheda',
)
sessions.append(session)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
for session in sessions:
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())

View file

@ -49,7 +49,7 @@ from django.views.generic import RedirectView
from ietf.doc.fields import SearchableDocumentsField
from ietf.doc.models import Document, State, DocEvent, NewRevisionDocEvent, DocAlias
from ietf.group.models import Group
from ietf.group.utils import can_manage_session_materials
from ietf.group.utils import can_manage_session_materials, can_manage_some_groups, can_manage_group
from ietf.person.models import Person
from ietf.person.name import plain_name
from ietf.ietfauth.utils import role_required, has_role
@ -82,6 +82,7 @@ from ietf.secr.proceedings.utils import handle_upload_file
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
create_recording)
from ietf.utils.decorators import require_api_key
from ietf.utils.history import find_history_replacements_active_at
from ietf.utils.log import assertion
from ietf.utils.mail import send_mail_message, send_mail_text
from ietf.utils.pipe import pipe
@ -96,7 +97,7 @@ from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSession
def get_interim_menu_entries(request):
'''Setup menu entries for interim meeting view tabs'''
entries = []
if has_role(request.user, ('Area Director','Secretariat','IRTF Chair','WG Chair', 'RG Chair')):
if can_manage_some_groups(request.user):
entries.append(("Upcoming", reverse("ietf.meeting.views.upcoming")))
entries.append(("Pending", reverse("ietf.meeting.views.interim_pending")))
if has_role(request.user, "Secretariat"):
@ -601,7 +602,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
session_parents = sorted(set(
s.group.parent for s in sessions
if s.group and s.group.parent and s.group.parent.type_id == 'area' or s.group.parent.acronym == 'irtf'
if s.group and s.group.parent and (s.group.parent.type_id == 'area' or s.group.parent.acronym == 'irtf')
), key=lambda p: p.acronym)
for i, p in enumerate(session_parents):
rgb_color = cubehelix(i, len(session_parents))
@ -609,7 +610,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
# dig out historic AD names
ad_names = {}
session_groups = set(s.group for s in sessions if s.group and s.group.parent.type_id == 'area')
session_groups = set(s.group for s in sessions if s.group and s.group.parent and s.group.parent.type_id == 'area')
meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
for group_id, history_time, name in Person.objects.filter(rolehistory__name='ad', rolehistory__group__group__in=session_groups, rolehistory__group__time__lte=meeting_time).values_list('rolehistory__group__group', 'rolehistory__group__time', 'name').order_by('rolehistory__group__time'):
@ -1508,7 +1509,7 @@ def meeting_requests(request, num=None):
s.current_status_name = status_names.get(s.current_status, s.current_status)
s.requested_by_person = session_requesters.get(s.requested_by)
groups_not_meeting = Group.objects.filter(state='Active',type__in=['wg','rg','ag','bof']).exclude(acronym__in = [session.group.acronym for session in sessions]).order_by("parent__acronym","acronym").prefetch_related("parent")
groups_not_meeting = Group.objects.filter(state='Active',type__in=['wg','rg','ag','bof','program']).exclude(acronym__in = [session.group.acronym for session in sessions]).order_by("parent__acronym","acronym").prefetch_related("parent")
return render(request, "meeting/requests.html",
{"meeting": meeting, "sessions":sessions,
@ -1532,9 +1533,22 @@ def session_details(request, num, acronym):
if not sessions:
raise Http404
# Find the time of the meeting, so that we can look back historically
# for what the group was called at the time.
meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
groups = list(set([ s.group for s in sessions ]))
group_replacements = find_history_replacements_active_at(groups, meeting_time)
status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
for session in sessions:
session.historic_group = None
if session.group:
session.historic_group = group_replacements.get(session.group_id)
if session.historic_group:
session.historic_group.historic_parent = None
session.type_counter = Counter()
ss = session.timeslotassignments.filter(schedule=meeting.schedule).order_by('timeslot__time')
if ss:
@ -1587,6 +1601,7 @@ def session_details(request, num, acronym):
'can_manage_materials' : can_manage,
'can_view_request': can_view_request,
'thisweek': datetime.date.today()-datetime.timedelta(days=7),
'now': datetime.datetime.now(),
})
class SessionDraftsForm(forms.Form):
@ -2392,8 +2407,12 @@ def interim_skip_announcement(request, number):
'meeting': meeting})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_pending(request):
if not can_manage_some_groups(request.user):
return HttpResponseForbidden()
'''View which shows interim meeting requests pending approval'''
meetings = data_for_meetings_overview(Meeting.objects.filter(type='interim').order_by('date'), interim_status='apprw')
@ -2411,8 +2430,12 @@ def interim_pending(request):
'meetings': meetings})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request(request):
if not can_manage_some_groups(request.user):
return HttpResponseForbidden("You don't have permission to request any interims")
'''View for requesting an interim meeting'''
SessionFormset = inlineformset_factory(
Meeting,
@ -2497,15 +2520,15 @@ def interim_request(request):
"formset": formset})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request_cancel(request, number):
'''View for cancelling an interim meeting request'''
meeting = get_object_or_404(Meeting, number=number)
first_session = meeting.session_set.first()
session_status = current_session_status(first_session)
group = first_session.group
if not can_view_interim_request(meeting, request.user):
if not can_manage_group(request.user, group):
return HttpResponseForbidden("You do not have permissions to cancel this meeting request")
session_status = current_session_status(first_session)
if request.method == 'POST':
form = InterimCancelForm(request.POST)
@ -2538,10 +2561,13 @@ def interim_request_cancel(request, number):
})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request_details(request, number):
'''View details of an interim meeting reqeust'''
'''View details of an interim meeting request'''
meeting = get_object_or_404(Meeting, number=number)
group = meeting.session_set.first().group
if not can_manage_group(request.user, group):
return HttpResponseForbidden("You do not have permissions to manage this meeting request")
sessions = meeting.session_set.all()
can_edit = can_edit_interim_request(meeting, request.user)
can_approve = can_approve_interim_request(meeting, request.user)
@ -2582,7 +2608,7 @@ def interim_request_details(request, number):
"can_approve": can_approve})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request_edit(request, number):
'''Edit details of an interim meeting reqeust'''
meeting = get_object_or_404(Meeting, number=number)
@ -2645,7 +2671,7 @@ def past(request):
def upcoming(request):
'''List of upcoming meetings'''
today = datetime.date.today()
today = datetime.date.today()-datetime.timedelta(days=7)
# Get ietf meetings starting 7 days ago, and interim meetings starting today
ietf_meetings = Meeting.objects.filter(type_id='ietf', date__gte=today-datetime.timedelta(days=7))
@ -2684,6 +2710,7 @@ def upcoming(request):
'menu_actions': actions,
'menu_entries': menu_entries,
'selected_menu_entry': selected_menu_entry,
'now': datetime.datetime.now()
})

View file

@ -1,12 +1,12 @@
[
{
"fields": {
"content": "{% autoescape off %}{{ assigner.ascii }} has assigned {{ reviewer.person.ascii }} as a reviewer for this document.\n\n{% if prev_team_reviews %}This team has completed other reviews of this document:{% endif %}{% for assignment in prev_team_reviews %}\n- {{ assignment.completed_on }} {{ assignment.reviewer.person.ascii }} -{% if assignment.reviewed_rev %}{{ assignment.reviewed_rev }}{% else %}{{ assignment.review_request.requested_rev }}{% endif %} {{ assignment.result.name }} \n{% endfor %}{% endautoescape %}\n",
"content": "{% autoescape off %}{{ assigner.ascii }} has assigned {{ reviewer.person.ascii }} as a reviewer for this document.\r\n\r\n{% if prev_team_reviews %}This team has completed other reviews of this document:{% endif %}{% for assignment in prev_team_reviews %}\r\n- {{ assignment.completed_on }} {{ assignment.reviewer.person.ascii }} -{% if assignment.reviewed_rev %}{{ assignment.reviewed_rev }}{% else %}{{ assignment.review_request.requested_rev }}{% endif %} {{ assignment.result.name }} \r\n{% endfor %}{% endautoescape %}",
"group": null,
"path": "/group/defaults/email/review_assigned.txt",
"title": "Default template for review assignment email",
"type": "django",
"variables": null
"variables": ""
},
"model": "dbtemplate.dbtemplate",
"pk": 354
@ -2484,6 +2484,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"lead\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": true,
@ -2514,6 +2515,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2544,6 +2546,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"Area Director\"]",
"groupman_roles": "[\"ad\",\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2574,6 +2577,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"ad\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"ad\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2604,6 +2608,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"ad\",\"secr\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2634,6 +2639,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2664,6 +2670,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2694,6 +2701,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2724,6 +2732,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2754,6 +2763,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"auth\"]",
"groupman_authroles": "[]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2784,6 +2794,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2814,6 +2825,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2844,6 +2856,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2874,6 +2887,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"advisor\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2903,21 +2917,22 @@
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"lead\",\"secr\"]",
"groupman_roles": "[\"lead\",\"secr\"]",
"docman_roles": "[\"lead\",\"chair\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"IAB\"]",
"groupman_roles": "[\"lead\",\"chair\",\"secr\"]",
"has_chartering_process": false,
"has_default_jabber": false,
"has_documents": true,
"has_meetings": false,
"has_meetings": true,
"has_milestones": true,
"has_nonsession_materials": false,
"has_reviews": false,
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"lead\",\"secr\"]",
"matman_roles": "[\"lead\",\"chair\",\"secr\"]",
"req_subm_approval": false,
"role_order": "[\"lead\",\"secr\"]",
"role_order": "[\"lead\",\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
@ -2934,6 +2949,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.review_requests",
"docman_roles": "[\"secr\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"ad\",\"secr\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2964,6 +2980,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2994,6 +3011,7 @@
"customize_workflow": true,
"default_tab": "ietf.group.views.group_documents",
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"IRTF Chair\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": true,
"has_default_jabber": true,
@ -3024,6 +3042,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"liaiman\",\"matman\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -3054,6 +3073,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -3084,6 +3104,7 @@
"customize_workflow": true,
"default_tab": "ietf.group.views.group_documents",
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"Area Director\"]",
"groupman_roles": "[\"ad\",\"chair\",\"delegate\",\"secr\"]",
"has_chartering_process": true,
"has_default_jabber": true,
@ -5603,8 +5624,8 @@
},
{
"fields": {
"editor_label": "(1)",
"desc": "",
"editor_label": "(1)",
"name": "Conflicts with",
"order": 0,
"penalty": 100000,
@ -5616,9 +5637,10 @@
{
"fields": {
"desc": "",
"editor_label": "time_relation",
"name": "Preference for time between sessions",
"order": 0,
"penalty": 100000,
"penalty": 1000,
"used": true
},
"model": "name.constraintname",
@ -5627,6 +5649,7 @@
{
"fields": {
"desc": "",
"editor_label": "timerange",
"name": "Can't meet within timerange",
"order": 0,
"penalty": 100000,
@ -5638,9 +5661,10 @@
{
"fields": {
"desc": "",
"editor_label": "wg_adjacent",
"name": "Request for adjacent scheduling with another WG",
"order": 0,
"penalty": 100000,
"penalty": 10000,
"used": true
},
"model": "name.constraintname",
@ -9065,7 +9089,7 @@
"desc": "IESG discussions on the document have raised some issues that need to be brought to the attention of the authors/WG, but those issues have not been written down yet. (It is common for discussions during a telechat to result in such situations. An AD may raise a possible issue during a telechat and only decide as a result of that discussion whether the issue is worth formally writing up and bringing to the attention of the authors/WG). A document stays in the \"Point Raised - Writeup Needed\" state until *ALL* IESG comments that have been raised have been documented.",
"name": "Point Raised - writeup needed",
"order": 1,
"used": true
"used": false
},
"model": "name.doctagname",
"pk": "point"
@ -10181,6 +10205,17 @@
"model": "name.importantdatename",
"pk": "idcutoff"
},
{
"fields": {
"default_offset_days": 70,
"desc": "Announcement of whether conditions have improved enough to hold an in-person meeting in Madrid, or if IETF 108 will be held as a virtual meeting",
"name": "IETF 108 Go-ahead Announcement",
"order": 0,
"used": false
},
"model": "name.importantdatename",
"pk": "ietf-108-go-ahead"
},
{
"fields": {
"default_offset_days": -82,
@ -11268,6 +11303,16 @@
"model": "name.rolename",
"pk": "chair"
},
{
"fields": {
"desc": "",
"name": "Communications Director",
"order": 0,
"used": true
},
"model": "name.rolename",
"pk": "comdir"
},
{
"fields": {
"desc": "",
@ -11420,7 +11465,7 @@
},
{
"fields": {
"desc": "Provides log-in permission to restricted Trac instances",
"desc": "Provides log-in permission to restricted Trac instances. Used by the generate_apache_perms management command, called from ../../scripts/Cron-runner",
"name": "Trac Editor",
"order": 0,
"used": true
@ -11500,8 +11545,8 @@
},
{
"fields": {
"desc": "WebEx support",
"name": "WebEx session",
"desc": "Web streaming support",
"name": "WebEx",
"order": 0,
"used": true
},
@ -14436,9 +14481,9 @@
"fields": {
"command": "xym",
"switch": "--version",
"time": "2020-02-19T00:13:43.554",
"time": "2020-05-10T00:12:51.809",
"used": true,
"version": "xym 0.4"
"version": "xym 0.4.8"
},
"model": "utils.versioninfo",
"pk": 1
@ -14447,9 +14492,9 @@
"fields": {
"command": "pyang",
"switch": "--version",
"time": "2020-02-19T00:13:44.450",
"time": "2020-05-10T00:12:53.489",
"used": true,
"version": "pyang 2.1.1"
"version": "pyang 2.2.1"
},
"model": "utils.versioninfo",
"pk": 2
@ -14458,9 +14503,9 @@
"fields": {
"command": "yanglint",
"switch": "--version",
"time": "2020-02-19T00:13:44.597",
"time": "2020-05-10T00:12:53.919",
"used": true,
"version": "yanglint 0.14.80"
"version": "yanglint SO 1.6.7"
},
"model": "utils.versioninfo",
"pk": 3
@ -14469,9 +14514,9 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
"time": "2020-02-19T00:13:45.481",
"time": "2020-05-10T00:12:56.462",
"used": true,
"version": "xml2rfc 2.40.0"
"version": "xml2rfc 2.44.0"
},
"model": "utils.versioninfo",
"pk": 4

View file

@ -353,7 +353,10 @@ class PersonalApiKey(models.Model):
def validate_key(cls, s):
import struct, hashlib, base64
assert isinstance(s, bytes)
key = base64.urlsafe_b64decode(s)
try:
key = base64.urlsafe_b64decode(s)
except Exception:
return None
id, salt, hash = struct.unpack(KEY_STRUCT, key)
k = cls.objects.filter(id=id)
if not k.exists():

View file

@ -29,7 +29,8 @@ from ietf.mailtrigger.utils import gather_address_lists
# -------------------------------------------------
# Globals
# -------------------------------------------------
AUTHORIZED_ROLES=('WG Chair','WG Secretary','RG Chair','IAB Group Chair','Area Director','Secretariat','Team Chair','IRTF Chair')
# TODO: This needs to be replaced with something that pays attention to groupfeatures
AUTHORIZED_ROLES=('WG Chair','WG Secretary','RG Chair','IAB Group Chair','Area Director','Secretariat','Team Chair','IRTF Chair','Program Chair','Program Lead','Program Secretary')
# -------------------------------------------------
# Helper Functions
@ -319,7 +320,10 @@ def confirm(request, acronym):
)
if 'resources' in form.data:
new_session.resources.set(session_data['resources'])
if int(form.data.get('joint_for_session', '-1')) == count:
jfs = form.data.get('joint_for_session', '-1')
if not jfs: # jfs might be ''
jfs = '-1'
if int(jfs) == count:
groups_split = form.cleaned_data.get('joint_with_groups').replace(',',' ').split()
joint = Group.objects.filter(acronym__in=groups_split)
new_session.joint_with_groups.set(joint)

View file

@ -771,6 +771,25 @@ ul.progress-section {
.btn .fa-stack { width: 1em; height: 1em; }
.btn .fa-stack .fa-stack-1x { line-height: 80%; }
.fa-stack-1 {
position: relative;
display: inline-block;
width: 1.28571429em;
height: 1em;
vertical-align: inherit;
}
.fa-stack-sm {
width: 100%;
text-align: center;
font-size: 0.7172em;
line-height: inherit;
}
.fa-stack-xs {
width: 100%;
text-align: center;
font-size: 0.5em;
line-height: inherit;
}
/* ========================================================================== */

View file

@ -0,0 +1,75 @@
This webfont is generated by http://fontello.com open source project.
================================================================================
Please, note, that you should obey original font licenses, used to make this
webfont pack. Details available in LICENSE.txt file.
- Usually, it's enough to publish content of LICENSE.txt file somewhere on your
site in "About" section.
- If your project is open-source, usually, it will be ok to make LICENSE.txt
file publicly available in your repository.
- Fonts, used in Fontello, don't require a clickable link on your site.
But any kind of additional authors crediting is welcome.
================================================================================
Comments on archive content
---------------------------
- /font/* - fonts in different formats
- /css/* - different kinds of css, for all situations. Should be ok with
twitter bootstrap. Also, you can skip <i> style and assign icon classes
directly to text elements, if you don't mind about IE7.
- demo.html - demo file, to show your webfont content
- LICENSE.txt - license info about source fonts, used to build your one.
- config.json - keeps your settings. You can import it back into fontello
anytime, to continue your work
Why so many CSS files ?
-----------------------
Because we like to fit all your needs :)
- basic file, <your_font_name>.css - is usually enough, it contains @font-face
and character code definitions
- *-ie7.css - if you need IE7 support, but still don't wish to put char codes
directly into html
- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
rules, but still wish to benefit from css generation. That can be very
convenient for automated asset build systems. When you need to update font -
no need to manually edit files, just override old version with archive
content. See fontello source code for examples.
- *-embedded.css - basic css file, but with embedded WOFF font, to avoid
CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`
server headers. But if you ok with dirty hack - this file is for you. Note,
that data url moved to separate @font-face to avoid problems with <IE9, when
string is too long.
- animate.css - use it to get ideas about spinner rotation animation.
Attention for server setup
--------------------------
You MUST setup server to reply with proper `mime-types` for font files -
otherwise some browsers will fail to show fonts.
Usually, `apache` already has necessary settings, but `nginx` and other
webservers should be tuned. Here is list of mime types for our file extensions:
- `application/vnd.ms-fontobject` - eot
- `application/x-font-woff` - woff
- `application/x-font-ttf` - ttf
- `image/svg+xml` - svg

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,58 @@
@font-face {
font-family: 'datatracker';
src: url('../font/datatracker.eot?60587374');
src: url('../font/datatracker.eot?60587374#iefix') format('embedded-opentype'),
url('../font/datatracker.woff2?60587374') format('woff2'),
url('../font/datatracker.woff?60587374') format('woff'),
url('../font/datatracker.ttf?60587374') format('truetype'),
url('../font/datatracker.svg?60587374#datatracker') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'datatracker';
src: url('../font/datatracker.svg?60587374#datatracker') format('svg');
}
}
*/
.dt {
font-family: "datatracker";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.fd-meetecho:before { content: '\e800'; } /* '' */

View file

@ -0,0 +1,48 @@
@font-face {
font-family: 'font-datatracker';
src: url('../font/font-datatracker.eot?60587374');
src: url('../font/font-datatracker.eot?60587374#iefix') format('embedded-opentype'),
url('../font/font-datatracker.woff2?60587374') format('woff2'),
url('../font/font-datatracker.woff?60587374') format('woff'),
url('../font/font-datatracker.ttf?60587374') format('truetype'),
url('../font/font-datatracker.svg?60587374#font-datatracker') format('svg');
font-weight: normal;
font-style: normal;
}
.fd {
font-family: "font-datatracker";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.fd-meetecho:before { content: '\e800'; } /* '' */

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Binary file not shown.

View file

@ -133,7 +133,8 @@ def validate_submission_rev(name, rev):
expected = 0
existing_revs = [int(i.rev) for i in Document.objects.filter(name=name) if i.rev and i.rev.isdigit() ]
log.assertion('[ i.rev for i in Document.objects.filter(name=name) if not (i.rev and i.rev.isdigit()) ]', [])
unexpected_revs = [ i.rev for i in Document.objects.filter(name=name) if not (i.rev and i.rev.isdigit()) ] # pyflakes:ignore
log.assertion('unexpected_revs', [])
if existing_revs:
expected = max(existing_revs) + 1

View file

@ -178,9 +178,9 @@
document attributes are necessary for an application is
available for all documents at the relative url <code>doc.json</code>, e.g.,
<code>{% url 'ietf.doc.views_doc.document_json' name='draft-ietf-poised95-std-proc-3' %}</code>
<a href="{% url 'ietf.doc.views_doc.document_json' name='draft-ietf-poised95-std-proc-3' %}"><span class="fa fa-external-link"/></a>.
<a href="{% url 'ietf.doc.views_doc.document_json' name='draft-ietf-poised95-std-proc-3' %}"><span class="fa fa-external-link"></span></a>.
You can also specify an RFC: <code>{% url 'ietf.doc.views_doc.document_json' name='rfc2026' %}</code>
<a href="{% url 'ietf.doc.views_doc.document_json' name='rfc2026' %}"><span class="fa fa-external-link"/></a>.
<a href="{% url 'ietf.doc.views_doc.document_json' name='rfc2026' %}"><span class="fa fa-external-link"></span></a>.
No API key is needed to access this.
</p>
@ -327,6 +327,7 @@
To verify a signature and get the verified data using Python with the
<a href="https://jwcrypto.readthedocs.io/en/latest/">jwcrypto</a> module,
you could do:
</p>
<pre>
from jwcrypto import jwk, jws
@ -339,7 +340,6 @@ jwstoken.deserialize(data)
jwstoken.verify(key)
payload = jwstoken.payload
</pre>
</p>
</div>
</div>

View file

@ -19,6 +19,7 @@
{% endcomment %}
<link rel="stylesheet" href="{% static 'font-awesome/css/font-awesome.min.css' %}">
<link rel="stylesheet" href="{% static 'ietf/font-datatracker/css/font-datatracker.css' %}">
<link rel="stylesheet" href="{% static 'ietf/bootstrap/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'ietf/bootstrap/css/bootstrap-theme.min.css' %}">
<link rel="stylesheet" href="{% static 'ietf/css/ietf.css' %}">

View file

@ -229,15 +229,12 @@
<span class="label label-danger pull-right">CANCELLED</span>
{% endif %}
{% if item.session.agenda %}
{% include "meeting/session_agenda_include.html" %}
{% endif %}
</td>
<td class="col-md-2">
<td class="col-md-2 text-right">
<span class="hidden-xs">
{% if item.timeslot.type.slug == 'other' %}
{% if item.session.agenda %}
{% include "meeting/session_buttons_include.html" %}
{% include "meeting/session_buttons_include.html" with show_agenda=True session=item.session meeting=schedule.meeting %}
{% else %}
{% for slide in item.session.slides %}
<a href="{{slide.get_href}}">{{ slide.title|clean_whitespace }}</a>
@ -332,12 +329,10 @@
<br><span class="text-danger">{{item.session.agenda_note}}</span>
{% endif %}
{% include "meeting/session_agenda_include.html" %}
</td>
<td class="text-nowrap text-right">
<span class="hidden-xs">
{% include "meeting/session_buttons_include.html" %}
{% include "meeting/session_buttons_include.html" with show_agenda=True session=item.session meeting=schedule.meeting %}
</span>
</td>
</tr>

View file

@ -39,7 +39,7 @@
<td>{{ meeting.date }}</td>
<td>{{ meeting.responsible_group.acronym }}</td>
<td>
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}</a>
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}</a>
</td>
</tr>
{% endfor %}

View file

@ -40,11 +40,7 @@
<td>{{ meeting.date }}</td>
<td>{{ meeting.responsible_group.acronym }}</td>
<td>
{% if meeting.type_id == "interim" %}
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %}&nbsp;&nbsp;<span class="label label-warning">CANCELLED</span>{% endif %}</a>
{% else %}
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">IETF - {{ meeting.number }}</a>
{% endif %}
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %}&nbsp;&nbsp;<span class="label label-warning">CANCELLED</span>{% endif %}</a>
</td>
<td>{% if meeting.can_approve %}<span class="label label-success">can be approved</span>{% endif %}</td>
</tr>

View file

@ -1,20 +1,26 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load textfilters %}
{% load origin %}
{% origin %}
{% if session.agenda %}
{% with session.official_timeslotassignment as item %}
{% load staticfiles %}
{% load textfilters %}
{% origin %}
{% if session.agenda and show_agenda %}
{% include "meeting/session_agenda_include.html" %}
<!-- agenda pop-up button -->
<a class="" data-toggle="modal" data-target="#modal-{{item.slug}}" title="Show meeting materials"><span class="fa fa-fw fa-arrows-alt"></span></a>
<!-- materials tar file -->
<a class="" href="/meeting/{{meeting.number}}/agenda/{{session.group.acronym}}-drafts.tgz" title="Download meeting materials as .tar archive"><span class="fa fa-fw fa-file-archive-o"></span></a>
<a class="" href="/meeting/{{meeting.number}}/agenda/{{session.historic_group.acronym}}-drafts.tgz" title="Download meeting materials as .tar archive"><span class="fa fa-fw fa-file-archive-o"></span></a>
<!-- materials PDF file -->
<a class="" href="/meeting/{{ meeting.number }}/agenda/{{session.group.acronym}}-drafts.pdf" title="Download meeting materials as PDF file"><span class="fa fa-fw fa-file-pdf-o"></span></a>
{% endwith %}
{% endif %}
<a class="" href="/meeting/{{ meeting.number }}/agenda/{{session.historic_group.acronym}}-drafts.pdf" title="Download meeting materials as PDF file"><span class="fa fa-fw fa-file-pdf-o"></span></a>
{% endif %}
<!-- etherpad -->
<a class="" href="https://etherpad.ietf.org:9009/p/notes-ietf-{{ meeting.number }}?useMonospaceFont=true" title="Etherpad for note-takers"><span class="fa fa-fw fa-edit"></span></a>
{% if item.timeslot.type.slug == 'plenary' %}
<a class="" href="https://etherpad.ietf.org:9009/p/notes-ietf-{{ meeting.number }}-plenary?useMonospaceFont=true" title="Etherpad for note-takers"><span class="fa fa-fw fa-edit"></span></a>
{% else %}
<a class="" href="https://etherpad.ietf.org:9009/p/notes-ietf-{{ meeting.number }}-{{session.historic_group.acronym}}?useMonospaceFont=true" title="Etherpad for note-takers"><span class="fa fa-fw fa-edit"></span></a>
{% endif %}
{# show stream buttons up till end of session, then show archive buttons #}
{% if now < item.timeslot.end_time %}
<!-- Jabber -->
<a class="" href="xmpp:{{session.jabber_room_name}}@jabber.ietf.org?join" title="Jabber room for {{session.jabber_room_name}}"><span class="fa fa-fw fa-lightbulb-o"></span></a>
<!-- webex -->
@ -26,17 +32,60 @@
{% elif session.remote_instructions|first_url %}
<a class=""
href="{{ session.remote_instructions|first_url }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
title="Session call-in"><span class="fa fa-fw fa-phone"></span>
</a>
{% elif item.timeslot.location.webex_url %}
<a class=""
href="{{item.timeslot.location.webex_url|format:session }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
</a>
<!-- Video stream (meetecho) -->
{% elif item.timeslot.location.video_stream_url %}
<a class=""
href="{{item.timeslot.location.video_stream_url|format:session }}"
title="Meetecho video stream"><span class="fa fa-fw fa-video-camera"></span>
</a>
{% else %}
<span class="">
<span class="fa fa-fw fa-phone" style="color: #ddd;"
title="No webex info found in remote instructions or agenda note"></span>
title="No webex or meetecho info found in remote instructions or agenda note"></span>
</span>
{% endif %}
{% else %}
<!-- Jabber -->
<a class="" href="https://www.ietf.org/jabber/logs/{{session.jabber_room_name}}?C=M;O=D" title="Jabber logs for {{session.jabber_room_name}}">
<span class="fa-stack-1"><i class="fa fa-fw fa-file-o fa-stack-1x"></i><i class="fa fa-fw fa-lightbulb-o fa-stack-sm"></i></span>
</a>
{% with session.recordings as recordings %}
{% if recordings %}
{# There's no guaranteed order, so this is a bit messy: #}
<!-- Audio -->
{% for r in recordings %}{% with href=r.get_href %}
{% if 'audio' in href %}
<a class="" href="{{ href }}" title="{{ r.title }}"><span class="fa fa-fw fa-file-audio-o"></span></a>
{% endif %}
{% endwith %}{% endfor %}
<!-- YouTube -->
{% for r in recordings %}{% with href=r.get_href %}
{% if 'youtu' in href %}
<a class="" href="{{ href }}" title="{{ r.title }}"><span class="fa fa-fw fa-file-video-o"></span></a>
{% endif %}
{% endwith %}{% endfor %}
<!-- Any other recordings -->
{% for r in recordings %}{% with href=r.get_href %}
{% if not 'audio' in href and not 'youtu' in href %}
<a class="" href="{{ href }}" title="{{ r.title }}"><span class="fa fa-fw fa-file-o"></span></a>
{% endif %}
{% endwith %}{% endfor %}
{% elif item.timeslot.location.video_stream_url %}
<a class=""
href="http://www.meetecho.com/ietf{{meeting.number}}/recordings#{{session.historic_group.acronym.upper}}"
title="Meetecho session recording"><span class="fd fa-fw fd-meetecho"></span></a>
{% elif show_empty %}
<span class="fa fa-fw"></span>
{% endif %}
{% endwith %}
{% endif %}

View file

@ -2,112 +2,95 @@
{% load origin %}
{% load staticfiles %}
{% load textfilters %}
{% origin %}
{% if item.session.agenda %}
<button class="btn btn-default btn-xs" data-toggle="modal" data-target="#modal-{{item.slug}}" title="Show meeting materials"><span class="fa fa-arrows-alt"></span></button>
<a class="btn btn-default btn-xs" href="/meeting/{{schedule.meeting.number}}/agenda/{{item.session.historic_group.acronym}}-drafts.tgz" title="Download meeting materials as .tar archive"><span class="fa fa-file-archive-o"></span></a>
<a class="btn btn-default btn-xs" href="/meeting/{{ schedule.meeting.number }}/agenda/{{item.session.historic_group.acronym}}-drafts.pdf" title="Download meeting materials as PDF file"><span class="fa fa-file-pdf-o"></span></a>
{% endif %}
<div id="session-buttons-{{session.pk}}">
{% if session.agenda and show_agenda %}
{% include "meeting/session_agenda_include.html" %}
<!-- agenda pop-up button -->
<a class="" data-toggle="modal" data-target="#modal-{{item.slug}}" title="Show meeting materials"><span class="fa fa-fw fa-arrows-alt"></span></a>
<!-- materials tar file -->
<a class="" href="/meeting/{{meeting.number}}/agenda/{{session.historic_group.acronym}}-drafts.tgz" title="Download meeting materials as .tar archive"><span class="fa fa-fw fa-file-archive-o"></span></a>
<!-- materials PDF file -->
<a class="" href="/meeting/{{ meeting.number }}/agenda/{{session.historic_group.acronym}}-drafts.pdf" title="Download meeting materials as PDF file"><span class="fa fa-fw fa-file-pdf-o"></span></a>
{% endif %}
{% if item.timeslot.type.slug == 'plenary' %}
<a class="btn btn-default btn-xs" href="https://etherpad.ietf.org:9009/p/notes-ietf-{{ schedule.meeting.number }}-plenary?useMonospaceFont=true" title="Etherpad for note-takers"><span class="fa fa-edit"></span></a>
{% else %}
<a class="btn btn-default btn-xs" href="https://etherpad.ietf.org:9009/p/notes-ietf-{{ schedule.meeting.number }}-{{item.session.historic_group.acronym}}?useMonospaceFont=true" title="Etherpad for note-takers"><span class="fa fa-edit"></span></a>
{% endif %}
<!-- etherpad -->
{% if item.timeslot.type.slug == 'plenary' %}
<a class="" href="https://etherpad.ietf.org:9009/p/notes-ietf-{{ meeting.number }}-plenary?useMonospaceFont=true" title="Etherpad for note-takers"><span class="fa fa-fw fa-edit"></span></a>
{% else %}
<a class="" href="https://etherpad.ietf.org:9009/p/notes-ietf-{{ meeting.number }}-{{session.historic_group.acronym}}?useMonospaceFont=true" title="Etherpad for note-takers"><span class="fa fa-fw fa-edit"></span></a>
{% endif %}
{# show stream buttons up till end of session, then show archive buttons #}
{% if now < item.timeslot.end_time %}
<!-- Jabber -->
<a class="btn btn-default btn-xs"
href="xmpp:{{item.session.jabber_room_name}}@jabber.ietf.org?join"
title="Jabber room for {{item.session.jabber_room_name}}"><span class="fa fa-lightbulb-o"></span>
</a>
<!-- Video stream (meetecho) -->
{% if item.timeslot.location.video_stream_url %}
<a class="btn btn-default btn-xs"
href="{{ item.timeslot.location.video_stream_url|format:item.session }}"
title="Meetecho video stream"><span class="fa fa-video-camera"></span>
</a>
{% endif %}
<!-- Audio stream -->
{% if item.timeslot.location.audio_stream_url %}
<a class="btn btn-default btn-xs"
href="{{item.timeslot.location.audio_stream_url|format:item.session }}"
title="Audio stream"><span class="glyphicon glyphicon-headphones"></span>
</a>
{% endif %}
<!-- WebEx -->
{% if "https://ietf.webex.com" in item.session.agenda_note|first_url %}
<a class="btn btn-default btn-xs"
href="{{ item.session.agenda_note|first_url }}"
title="Webex session"><span class="fa fa-phone"></span>
</a>
{% else %}
{% if item.timeslot.location.webex_url %}
<a class="btn btn-default btn-xs"
href="{{item.timeslot.location.webex_url|format:item.session }}"
title="Webex session"><span class="fa fa-phone"></span>
</a>
{% endif %}
{% endif %}
{% else %}
<!-- Jabber logs -->
{% if schedule.meeting.number|add:"0" >= 60 %}
<a class="btn btn-default btn-xs"
href="https://www.ietf.org/jabber/logs/{{item.session.jabber_room_name}}"
title="Jabber logs for {{item.session.jabber_room_name}}">
<span class="fa fa-stack">
<i class="fa fa-file-o fa-stack-1x"></i>
<i class="fa fa-lightbulb-o fa-stack-1x"></i>
</span>
</a>
{% endif %}
<!-- Video recording -->
{% if schedule.meeting.number|add:"0" >= 80 %}
{% with item.session.recordings as recordings %}
{% if recordings %}
<!-- There's no guaranteed order, so this is a bit messy: -->
<!-- First, the audio recordings, if any -->
{% for r in recordings %}
{% if r.get_href and 'audio' in r.get_href %}
<a class="btn btn-default btn-xs" href="{{ r.get_href }}" title="{{ r.title}}">
<span class="fa fa-file-audio-o"></span>
</a>
{% endif %}
{% endfor %}
<!-- Then the youtube recordings -->
{% for r in recordings %}
{% if r.get_href and 'youtu' in r.get_href %}
<a class="btn btn-default btn-xs" href="{{ r.get_href }}" title="{{ r.title }}">
<span class="fa fa-file-video-o"></span>
</a>
{% endif %}
{% endfor %}
<!-- Finally, any other recordings -->
{% for r in recordings %}
{% if r.get_href and not 'audio' in r.get_href and not 'youtu' in r.get_href %}
<a class="btn btn-default btn-xs" href="{{ r.get_href }}" title="{{ r.title }}">
<span class="fa fa-file-o"></span>
</a>
{% endif %}
{% endfor %}
{% endif %}
{% if item.timeslot.location.video_stream_url %}
<a class="btn btn-default btn-xs"
href="http://www.meetecho.com/ietf{{schedule.meeting.number}}/recordings#{{item.session.historic_group.acronym.upper}}"
title="Meetecho session recording"><img src="{% static 'ietf/images/meetecho-icon.svg' %}" alt="Meetecho Stream" width="12px">
</a>
{# show stream buttons up till end of session, then show archive buttons #}
{% if now < item.timeslot.end_time %}
<!-- Jabber -->
<a class=""
href="xmpp:{{session.jabber_room_name}}@jabber.ietf.org?join"
title="Jabber room for {{session.jabber_room_name}}"><span class="fa fa-fw fa-lightbulb-o"></span></a>
<!-- Video stream (meetecho) -->
{% if item.timeslot.location.video_stream_url %}
<a class=""
href="{{item.timeslot.location.video_stream_url|format:session }}"
title="Meetecho video stream"><span class="fa fa-fw fa-video-camera"></span>
</a>
{% endif %}
<!-- Audio stream -->
{% if item.timeslot.location.audio_stream_url %}
<a class=""
href="{{item.timeslot.location.audio_stream_url|format:session }}"
title="Audio stream"><span class="glyphicon glyphicon-headphones"></span>
</a>
{% endif %}
<!-- WebEx -->
{% if "webex.com/" in session.agenda_note|first_url %}
<a class=""
href="{{ session.agenda_note|first_url }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
</a>
{% elif item.timeslot.location.webex_url %}
<a class=""
href="{{item.timeslot.location.webex_url|format:session }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
</a>
{% endif %}
{% else %}
<!-- Jabber logs -->
{% if meeting.number|add:"0" >= 60 %}
<a class="" href="https://www.ietf.org/jabber/logs/{{session.jabber_room_name}}?C=M;O=D" title="Jabber logs for {{session.jabber_room_name}}">
<span class="fa-stack-1"><i class="fa fa-fw fa-file-o fa-stack-1x"></i><i class="fa fa-fw fa-lightbulb-o fa-stack-sm"></i></span>
</a>
{% endif %}
<!-- Recordings -->
{% if meeting.number|add:"0" >= 80 %}
{% with session.recordings as recordings %}
{% if recordings %}
{# There's no guaranteed order, so this is a bit messy: #}
<!-- First, the audio recordings, if any -->
{% for r in recordings %}
{% if r.get_href and 'audio' in r.get_href %}
<a class="" href="{{ r.get_href }}" title="{{ r.title}}"><span class="fa fa-fw fa-file-audio-o"></span></a>
{% endif %}
{% endwith %}
{% endif %}
<!-- Audio recording -->
{# <a class="btn btn-default btn-xs" href="" title="Audio recording"><span class="fa fa-file-audio-o"></span></a>#}
{% endif %}
{% endfor %}
<!-- Then the youtube recordings -->
{% for r in recordings %}
{% if r.get_href and 'youtu' in r.get_href %}
<a class="" href="{{ r.get_href }}" title="{{ r.title }}"><span class="fa fa-fw fa-file-video-o"></span></a>
{% endif %}
{% endfor %}
<!-- Finally, any other recordings -->
{% for r in recordings %}
{% if r.get_href and not 'audio' in r.get_href and not 'youtu' in r.get_href %}
<a class="" href="{{ r.get_href }}" title="{{ r.title }}"><span class="fa fa-fw fa-file-o"></span></a>
{% endif %}
{% endfor %}
{% endif %}
{% endwith %}
{% if item.timeslot.location.video_stream_url %}
<a class=""
href="http://www.meetecho.com/ietf{{meeting.number}}/recordings#{{session.historic_group.acronym.upper}}"
title="Meetecho session recording"><span class="fd fa-fw fd-meetecho"></span></a>
{% endif %}
{% endif %}
{% endif %}
</div>

View file

@ -17,7 +17,7 @@
<h1>{{ meeting }} : {{ acronym }}
{% if meeting.date >= thisweek %}
<a class="regular pull-right" title="icalendar entry for {{acronym}}@{{meeting.number}}" href="{% url 'ietf.meeting.views.ical_agenda' num=meeting.number acronym=acronym %}"><span class="fa fa-calendar"></span></a>
<a class="regular pull-right" title="icalendar entry for {{acronym}}@{{meeting.number}}" href="{% url 'ietf.meeting.views.ical_agenda' num=meeting.number acronym=acronym %}"><span class="fa fa-fw fa-calendar"></span> </a>
{% endif %}
</h1>

View file

@ -1,144 +1,158 @@
{% load origin ietf_filters textfilters tz dateformat %}{% origin %}
{% for session in sessions %}
<h2 class="anchor-target" id="session_{{session.pk}}">{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}
{% for time in session.times %}{% if not forloop.first %}, {% endif %} {{time|dateformat:"l Y-m-d H:i T"}} {% if time.tzinfo.zone != "UTC" %}<span class="small">({{time|utc|dateformat:"H:i T"}})</span>{% endif %}{% endfor %}
{% if session.cancelled %}<small class="label label-warning">CANCELLED</small>{% else %}{{ session.status }}{% endif %}
{% if session.name %} : {{ session.name }}{% endif %}
{% if meeting.date >= thisweek %}
<a class="regular pull-right" title="icalendar entry for {{acronym}} session {{ forloop.counter }}" href="{% url 'ietf.meeting.views.ical_agenda' num=meeting.number session_id=session.id %}"><span class="fa fa-calendar"></span></a>
{% endif %}
</h2>
{% if session.agenda_note %}<h3>{{session.agenda_note}}</h3>{% endif %}
{% with item=session.official_timeslotassignment %}
<h2 class="anchor-target" id="session_{{session.pk}}">{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}
{% for time in session.times %}{% if not forloop.first %}, {% endif %} {{time|dateformat:"l Y-m-d H:i T"}} {% if time.tzinfo.zone != "UTC" %}<span class="small">({{time|utc|dateformat:"H:i T"}})</span>{% endif %}{% endfor %}
{% if session.cancelled %}<small class="label label-warning">CANCELLED</small>{% else %}{{ session.status }}{% endif %}
{% if session.name %} : {{ session.name }}{% endif %}
{% if not session.cancelled %}
<span class="regular pull-right">
{% if meeting.type.slug == 'interim' %}
{% include "meeting/interim_session_buttons.html" with show_agenda=False show_empty=False %}
{% else %}
{% with schedule=meeting.schedule %}
{% include "meeting/session_buttons_include.html" %}
{% endwith %}
{% endif %}
<!-- iCalendar item -->
{% if now < item.timeslot.end_time %}
<a title="icalendar entry for {{acronym}} session {{ forloop.counter }}" href="{% url 'ietf.meeting.views.ical_agenda' num=meeting.number session_id=session.id %}"><span class="fa fa-fw fa-calendar-o"></span> </a>
{% endif %}
</span>
{% endif %}
</h2>
{% if session.agenda_note %}<h3>{{session.agenda_note}}</h3>{% endif %}
{% if can_manage_materials %}
{% if session.current_status == 'sched' or session.current_status == 'schedw' %}
<div class="buttonlist">
{% if can_view_request %}
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">Meeting Details</a>
{% if can_manage_materials %}
{% if session.current_status == 'sched' or session.current_status == 'schedw' %}
<div class="buttonlist">
{% if can_view_request %}
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">Meeting Details</a>
{% endif %}
</div>
{% if not session.type_counter.agenda %}
<span class="label label-warning">This session does not yet have an agenda</span>
{% endif %}
{% endif %}
{% endif %}
{% if meeting.type.slug == 'interim' and session.remote_instructions %}
<div>
<b>Remote instructions:</b>
{% if "https://ietf.webex.com" in session.agenda_note|first_url %}
<a class=""
href="{{ session.agenda_note|first_url }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
</a>
{% elif "https://ietf.webex.com" in session.remote_instructions|first_url %}
<a class=""
href="{{ session.remote_instructions|first_url }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
</a>
{% endif %}
{{ session.remote_instructions }}
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">Agenda, Minutes, and Bluesheets</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_artifacts %}
<tr>
{% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %}
<td>
<a href="{{pres.document.get_href}}">{{pres.document.title}}</a>
<a href="{{url}}">({{ pres.document.name }})</a>
</td>
{% if user|has_role:"Secretariat" or can_manage_materials %}
<td class="col-md-2">
{% if pres.document.type.slug == 'minutes' %}
{% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number as upload_url %}
{% elif pres.document.type.slug == 'agenda' %}
{% url 'ietf.meeting.views.upload_session_agenda' session_id=session.pk num=session.meeting.number as upload_url %}
{% else %}
{% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number as upload_url %}
{% endif %}
{% if pres.document.type.slug != 'bluesheets' or user|has_role:"Secretariat" or meeting.type.slug == 'interim' and can_manage_materials %}
<a class="btn btn-default btn-sm pull-right" href="{{upload_url}}">Upload Revision</a>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if can_manage_materials %}
{% if not session.type_counter.agenda %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_agenda' session_id=session.pk num=session.meeting.number %}">Upload Agenda</a>
{% endif %}
{% if not session.type_counter.minutes %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number %}">Upload Minutes</a>
{% endif %}
{% endif %}
{% if user|has_role:"Secretariat" and not session.type_counter.bluesheets or meeting.type.slug == 'interim' and can_manage_materials and not session.type_counter.bluesheets %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number %}">Upload Bluesheets</a>
{% endif %}
</div>
{% if not session.type_counter.agenda %}
<span class="label label-warning">This session does not yet have an agenda</span>
{% endif %}
{% endif %}
{% endif %}
{% if meeting.type.slug == 'interim' and session.remote_instructions %}
<div>
<b>Remote instructions:</b>
{% if "https://ietf.webex.com" in session.agenda_note|first_url %}
<a class=""
href="{{ session.agenda_note|first_url }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
</a>
{% elif "https://ietf.webex.com" in session.remote_instructions|first_url %}
<a class=""
href="{{ session.remote_instructions|first_url }}"
title="Webex session"><span class="fa fa-fw fa-phone"></span>
</a>
{% endif %}
{{ session.remote_instructions }}
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">Agenda, Minutes, and Bluesheets</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_artifacts %}
<tr>
{% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %}
<td>
<a href="{{pres.document.get_href}}">{{pres.document.title}}</a>
<a href="{{url}}">({{ pres.document.name }})</a>
</td>
{% if user|has_role:"Secretariat" or can_manage_materials %}
<td class="col-md-2">
{% if pres.document.type.slug == 'minutes' %}
{% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number as upload_url %}
{% elif pres.document.type.slug == 'agenda' %}
{% url 'ietf.meeting.views.upload_session_agenda' session_id=session.pk num=session.meeting.number as upload_url %}
{% else %}
{% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number as upload_url %}
{% endif %}
{% if pres.document.type.slug != 'bluesheets' or user|has_role:"Secretariat" or meeting.type.slug == 'interim' and can_manage_materials %}
<a class="btn btn-default btn-sm pull-right" href="{{upload_url}}">Upload Revision</a>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if can_manage_materials %}
{% if not session.type_counter.agenda %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_agenda' session_id=session.pk num=session.meeting.number %}">Upload Agenda</a>
{% endif %}
{% if not session.type_counter.minutes %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number %}">Upload Minutes</a>
{% endif %}
{% endif %}
{% if user|has_role:"Secretariat" and not session.type_counter.bluesheets or meeting.type.slug == 'interim' and can_manage_materials and not session.type_counter.bluesheets %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number %}">Upload Bluesheets</a>
{% endif %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" data-toggle="tooltip" title="Drag and drop to reorder slides">Slides</div>
<div class="panel-body">
<table class="table table-condensed table-striped slides" id="slides_{{session.pk}}">
<tbody session="{{session.pk}}" addToSession="{% url 'ietf.meeting.views.ajax_add_slides_to_session' session_id=session.pk num=session.meeting.number %}" removeFromSession="{% url 'ietf.meeting.views.ajax_remove_slides_from_session' session_id=session.pk num=session.meeting.number %}" reorderInSession="{% url 'ietf.meeting.views.ajax_reorder_slides_in_session' session_id=session.pk num=session.meeting.number %}">
{% for pres in session.filtered_slides %}
<tr name="{{pres.document.name}}">
{% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %}
<td>
<a href="{{pres.document.get_href}}">{{pres.document.title}} </a>
<a href="{{url}}">({{ pres.document.name }}) </a>
</td>
{% if can_manage_materials %}
<td class="col-md-2">
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.upload_session_slides' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Upload Revision</a>
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.remove_sessionpresentation' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Remove</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% if can_manage_materials %}
<a id="uploadslides" class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_slides' session_id=session.pk num=session.meeting.number %}">Upload New Slides</a>
{% elif request.user.is_authenticated and not session.is_material_submission_cutoff %}
<a id="proposeslides" class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.propose_session_slides' session_id=session.pk num=session.meeting.number %}">Propose Slides</a>
{% endif %}
</div>
{% if can_manage_materials %}
<div class="panel-footer small">Drag-and-drop to reorder slides</div>
{% endif %}
</div>
<div class="panel panel-default">
<div class="panel-heading">Drafts
</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_drafts %}
<tr>
<td>
<div class="panel panel-default">
<div class="panel-heading" data-toggle="tooltip" title="Drag and drop to reorder slides">Slides</div>
<div class="panel-body">
<table class="table table-condensed table-striped slides" id="slides_{{session.pk}}">
<tbody session="{{session.pk}}" addToSession="{% url 'ietf.meeting.views.ajax_add_slides_to_session' session_id=session.pk num=session.meeting.number %}" removeFromSession="{% url 'ietf.meeting.views.ajax_remove_slides_from_session' session_id=session.pk num=session.meeting.number %}" reorderInSession="{% url 'ietf.meeting.views.ajax_reorder_slides_in_session' session_id=session.pk num=session.meeting.number %}">
{% for pres in session.filtered_slides %}
<tr name="{{pres.document.name}}">
{% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %}
<a href="{{url}}">{{pres.document.title}} ({{ pres.document.name }}) {% if pres.rev %}Version {{pres.rev}}{% endif %}</a>
</td>
{% if can_manage_materials %}
<td class="col-md-2">
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.remove_sessionpresentation' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Remove</a>
<td>
<a href="{{pres.document.get_href}}">{{pres.document.title}} </a>
<a href="{{url}}">({{ pres.document.name }}) </a>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if can_manage_materials %}
<td class="col-md-2">
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.upload_session_slides' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Upload Revision</a>
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.remove_sessionpresentation' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Remove</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% if can_manage_materials %}
<a id="uploadslides" class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_slides' session_id=session.pk num=session.meeting.number %}">Upload New Slides</a>
{% elif request.user.is_authenticated and not session.is_material_submission_cutoff %}
<a id="proposeslides" class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.propose_session_slides' session_id=session.pk num=session.meeting.number %}">Propose Slides</a>
{% endif %}
</div>
{% if can_manage_materials %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.add_session_drafts' session_id=session.pk num=session.meeting.number %}">
Link additional drafts to session
</a>
<div class="panel-footer small">Drag-and-drop to reorder slides</div>
{% endif %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Drafts
</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_drafts %}
<tr>
<td>
{% url 'ietf.doc.views_doc.document_main' name=pres.document.name as url %}
<a href="{{url}}">{{pres.document.title}} ({{ pres.document.name }}) {% if pres.rev %}Version {{pres.rev}}{% endif %}</a>
</td>
{% if can_manage_materials %}
<td class="col-md-2">
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.remove_sessionpresentation' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Remove</a>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if can_manage_materials %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.add_session_drafts' session_id=session.pk num=session.meeting.number %}">
Link additional drafts to session
</a>
{% endif %}
</div>
</div>
{% endwith %}
{% endfor %}

View file

@ -20,7 +20,14 @@
<div class="row">
<div class="col-md-10">
<h1>Upcoming Meetings</h1>
<h1>Upcoming Meetings
<span class="regular pull-right">
<a title="iCalendar subscription for upcoming meetings" href="webcal://{{request.get_host}}{% url 'ietf.meeting.views.upcoming_ical' %}">
<span class="fa fa-stack-1"><i class="fa fa-fw fa-calendar-o fa-stack-1x"></i><i class="fa fa-fw fa-repeat fa-stack-xs"></i></span>
</a>
<a title="iCalendar entry for upcoming meetings" href="{% url 'ietf.meeting.views.upcoming_ical' %}"><span class="fa fa-calendar"></span></a>
</span>
</h1>
<p>For more on regular IETF meetings see <a href="https://www.ietf.org/meeting/upcoming.html">here</a></p>
@ -60,7 +67,7 @@
{% with meeting=entry %}
<td>{{ meeting.date }} - {{ meeting.end }}</td>
<td>ietf</td>
<td><a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF {{ meeting.number }}</a></td>
<td><a class="ietf-meeting-link" href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF {{ meeting.number }}</a></td>
<td></td>
{% endwith %}
{% elif entry|classname == 'Session' %}
@ -68,11 +75,17 @@
<td>{{ session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}} - {{ session.official_timeslotassignment.timeslot.utc_end_time | date:"H:i e" }}</td>
<td><a href="{% url 'ietf.group.views.group_home' acronym=session.group.acronym %}">{{ session.group.acronym }}</a></td>
<td>
<a href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}">{{ session.meeting.number }}{% if session.current_status == 'canceled' %}&nbsp;&nbsp;<span class="label label-warning">CANCELLED</span>{% endif %}</a>
</td>
<td class='text-right'>
{% include "meeting/interim_session_buttons.html" %}
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}">{{ session.meeting.number }}</a>
</td>
{% if session.current_status == 'canceled' %}
<td class='text-right'>
<span class="label label-warning">CANCELLED</span>
</td>
{% else %}
<td class='text-right'>
{% include "meeting/interim_session_buttons.html" with show_agenda=True %}
</td>
{% endif %}
{% endwith %}
{% else %}
<td><span class="label-warning">Unexpected entry type: {{entry|classname}}</span></td>

View file

@ -63,7 +63,7 @@
<h2 class="anchor-target" id="references">References</h2>
<ul>
<li><a href="https://datatracker.ietf.org/doc/rfc2026/">The Internet Standards Process (RFC 2026)</a></li>
<li><a href="https://datatracker.ietf.org/doc/rfc8713/">IAB, IESG, IETF Trust, and IETF LLC Selection, Confirmation, and Recall Process: Operation of the IETF Nominating and Recall Committees (RFC 8713) (Also BCP10)
<li><a href="https://datatracker.ietf.org/doc/rfc8713/">IAB, IESG, IETF Trust, and IETF LLC Selection, Confirmation, and Recall Process: Operation of the IETF Nominating and Recall Committees (RFC 8713) (Also BCP10)</a></li>
<li><a href="https://datatracker.ietf.org/doc/rfc3797/">Publicly Verifiable Nominations Committee (NomCom) Random Selection (RFC 3797)</a></li>
</ul>

View file

@ -84,8 +84,10 @@
{% if alias_data %}
<table class="table table-condensed">
<thead>
<th>Affiliation</th>
<th>Alias</th>
<tr>
<th>Affiliation</th>
<th>Alias</th>
</tr>
</thead>
{% for name, alias in alias_data %}

View file

@ -1,9 +1,13 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}{% load origin %}{% origin %}
<p>
Please send reports about submission problems to
<a href="mailto:ietf-draft-submission@ietf.org">ietf-action@ietf.org</a>, or, if you feel
this is a bug, please report it to the Tools Team using the Bug Report links at the bottom
of the page.
Please send reports about submission tool bugs to the Tools Team using one
of the Bug Report links at the bottom of the page.
</p>
<p>
If you need to request manual posting of an Internet-Draft, please send the
draft and the reason for manual posting to <a href="mailto:idsubmission@ietf.org">idsubmission@ietf.org</a>.
</p>

View file

@ -162,14 +162,7 @@
<h3>Problem report</h3>
<p>
Please send reports about submission problems to
<a href="mailto:ietf-draft-submission@ietf.org">ietf-action@ietf.org</a>, or, if you feel
there is a bug, please report it to the Tools Team using the Bug Report links at the bottom
of the page.
</p>
{% include "submit/problem-reports-footer.html" %}
<p>
The specification for this tool can be found in <a href="https://www.ietf.org/rfc/rfc4228.txt?number=4228">RFC 4228</a>.