Merged in an update from trunk@9942.

- Legacy-Id: 9961
This commit is contained in:
Henrik Levkowetz 2015-08-03 14:12:38 +00:00
commit 11411d2c30
34 changed files with 1865 additions and 125 deletions

View file

@ -2,7 +2,6 @@
import os, sys, re, datetime, argparse, traceback, tempfile, json, subprocess
import html5lib
import debug # pyflakes:ignore
import random
# Set up import path to find our own Django
@ -33,6 +32,8 @@ parser.add_argument('--random', action='store_true',
parser.add_argument('--validate-all', dest='validate_all', action='store_true', default=False,
help='Run html 5 validation on all pages, without skipping similar urls. '
'(The default is to only run validation on one of /foo/1/, /foo/2/, /foo/3/, etc.)')
parser.add_argument('-v', '--verbose', action='store_true', default=False,
help='Be more verbose')
args = parser.parse_args()
@ -44,6 +45,9 @@ import django.test
django.setup()
# This needs to come after we set up sys path to include the local django
import debug # pyflakes:ignore
# prevent memory from leaking when settings.DEBUG=True
from django.db import connection
class DontSaveQueries(object):
@ -59,6 +63,11 @@ MAX_URL_LENGTH = 500
# --- Functions ---
def note(s):
if args.verbose:
sys.stderr.write(s)
sys.stderr.write('\n')
def strip_url(url):
if url.startswith("http://testserver"):
url = url[len("http://testserver"):]
@ -105,26 +114,45 @@ def extract_tastypie_urls(content):
def check_html_valid(url, response, args):
global parser, validated_urls, doc_types, warnings
# These URLs have known issues, skip them until those are fixed
if re.search('(/secr|admin/)|/doc/.*/edit/info/', url):
log("%s blacklisted; skipping HTML validation" % url)
return
key = url
if not args.validate_all:
# derive a key for urls like this by replacing primary keys
key = re.sub("/[0-9.]+/", "/nnnn/", key)
key = re.sub("/.+@.+/", "/x@x.org/", key)
key = re.sub("#.*$", "", key)
key = re.sub("\?.*$", "", key)
key = re.sub("/rfc[0-9]+/", "/rfcnnnn/", key)
key = re.sub("/wg/[a-z0-9-]+/", "/wg/foo/", key)
key = re.sub("/rg/[a-z0-9-]+/", "/rg/foo/", key)
key = re.sub("/ipr/[0-9]+/", "/ipr/nnnn/", key)
key = re.sub("#.*$", "", key)
key = re.sub("/.+@.+/", "/x@x.org/", key)
key = re.sub("/[0-9.]+/", "/nnnn/", key)
key = re.sub("/[0-9.]+/", "/mmmm/", key)
key = re.sub("/ag/[a-z0-9-]+/", "/ag/foo/", key)
key = re.sub("/area/[a-z0-9-]+/", "/area/foo/", key)
key = re.sub("/bcp[0-9]+/", "/bcpnnn/", key)
key = re.sub("/conflict-review-[a-z0-9-]+/", "/conflrev-foo/", key)
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("/ipr/search/.*", "/ipr/search/", key)
key = re.sub("/release/[0-9dev.]+/", "/release/n.n.n/", key)
key = re.sub("/rfc[0-9]+/", "/rfcnnnn/", key)
key = re.sub("/rg/[a-z0-9-]+/", "/rg/foo/", key)
key = re.sub("/secr/srec/nnnn/[0-9a-z-]+/", "/secr/sreq/nn/bar/", key)
key = re.sub("/state/[a-z0-9-]+/", "/state/foo/", key)
key = re.sub("/state/[a-z0-9-]+/[a-z0-9-]+/", "/state/foo/bar/", key)
key = re.sub("/status-change-[a-z0-9-]+/", "/statchg-foo/", key)
key = re.sub("/std[0-9]+/", "/stdnnn/", key)
key = re.sub("/submit/status/nnnn/[0-9a-f]+/", "/submit/status/nnnn/bar/", key)
key = re.sub("/team/[a-z0-9-]+/", "/team/foo/", key)
key = re.sub("/wg/[a-z0-9-]+/", "/wg/foo/", key)
for slug in doc_types:
key = re.sub("/%s-.*/"%slug, "/%s-nnnn/"%slug, key)
if not key in validated_urls:
note('Validate: %-32s: %s' % (url[:32], key))
# These URLs have known issues, skip them until those are fixed
if re.search('(/secr|admin/)|/doc/.*/edit/info/', url):
log("%s blacklisted; skipping HTML validation" % url)
validated_urls[key] = True
return
if hasattr(response, "content"):
content = response.content
else:
@ -156,6 +184,14 @@ def check_html_valid(url, response, args):
(pos, code))
warnings += 1
def skip_url(url):
for pattern in (
"^/community/[0-9]+/remove_document/",
"^/community/personal/",
):
if re.search(pattern, url):
return True
return False
def log(s):
print(s)
@ -243,6 +279,9 @@ if __name__ == "__main__":
visited.add(url)
if skip_url(url):
continue
try:
timestamp = datetime.datetime.now()
r = client.get(url, secure=True, follow=True)
@ -298,7 +337,7 @@ if __name__ == "__main__":
log("=============")
else:
tags.append(u"FAIL for %s\n (from %s)" % (url, referrer))
tags.append(u"FAIL (from %s)" % (referrer, ))
errors += 1
if elapsed.total_seconds() > slow_threshold:

View file

@ -1,3 +1,40 @@
ietfdb (6.3.0) ietf; urgency=medium
**Active area, directorate, team, and area group pages**
This release provides new group overview pages for active areas, area
groups, teams, and directorates. It also improves the error reporting when
drafts are submitted with invalid XML, providing line numbers and specifics
for the issues found. Additionally, there are a few bug fixes.
* Merged in [9924] from rjsparks@nostrum.com:
Added views of active areas, area groups, teams, and directorates. Added
navigation to those views to the base menus. Unified the URL patterns
shared between group/urls and group/urls_info, exposing the same view at,
e.g., /group/stir and /wg/stir/. Improved testing, primarily of
group/info.py
* Merged in [9901] from rcross@amsl.com:
Fixed matching audio file names with rooms.
* Provided a document's rfc number (if any) as part of the document fields
exposed in the json api.
* Changed the code for meta-data extraction from xml files to not break
if a sought-after element is missing.
* Updated the meta-data error message to say 'errors ... below' instead of
'errors ... above', to match the relative placement of text and error
indications.
* Improved the error reporting for invalid xml file submissions.
* Improved the handling of failed xml2rfc conversions when no txt document
is submitted.
-- Henrik Levkowetz <henrik@levkowetz.com> 01 Aug 2015 14:52:37 +0000
ietfdb (6.2.0) ietf; urgency=medium
**XML-Only Draft Submission**

View file

@ -2,13 +2,13 @@
import checks # pyflakes:ignore
# Don't add patch number here:
__version__ = "6.2.1.dev0"
__version__ = "6.3.1.dev0"
# set this to ".p1", ".p2", etc. after patching
__patch__ = ""
__date__ = "$Date$"
__rev__ = "$Rev$ (dev) Latest release: Rev. 9880 "
__rev__ = "$Rev$ (dev) Latest release: Rev. 9940 "
__id__ = "$Id$"

View file

@ -179,7 +179,7 @@ def generate_approval_mail_approved(request, doc):
doc.group.name_with_wg = doc.group.name + " Working Group"
if doc.group.list_email:
cc.append("%s mailing list <%s>" % (doc.group.acronym, doc.group.list_email))
cc.append("%s chair <%s-chairs@tools.ietf.org>" % (doc.group.acronym, doc.group.acronym))
cc.append("%s chair <%s-chairs@ietf.org>" % (doc.group.acronym, doc.group.acronym))
else:
doc.group.name_with_wg = doc.group.name

View file

@ -1,6 +1,6 @@
# Autogenerated by the mkresources management command 2014-12-14 19:50
from tastypie.resources import ModelResource
from tastypie.fields import ToOneField, ToManyField
from tastypie.fields import ToOneField, ToManyField, CharField
from tastypie.constants import ALL, ALL_WITH_RELATIONS
from ietf import api
@ -86,6 +86,7 @@ class DocumentResource(ModelResource):
states = ToManyField(StateResource, 'states', null=True)
tags = ToManyField(DocTagNameResource, 'tags', null=True)
authors = ToManyField(EmailResource, 'authors', null=True)
rfc = CharField(attribute='rfc_number', null=True)
class Meta:
queryset = Document.objects.all()
#resource_name = 'document'

View file

@ -0,0 +1,14 @@
from django import template
from django.template.loader import render_to_string
from ietf.name.models import GroupTypeName
register = template.Library()
@register.simple_tag
def active_groups_menu():
parents = GroupTypeName.objects.filter(slug__in=['ag','area','team','dir'])
for p in parents:
p.menu_url = '/%s/'%p.slug
return render_to_string('base/menu_active_groups.html', { 'parents': parents })

View file

@ -467,7 +467,7 @@ def collect_email_addresses(emails, doc):
if role.email.address not in emails:
emails[role.email.address] = '"%s"' % (role.person.name)
if doc.group.type.slug == 'wg':
address = '%s-ads@tools.ietf.org' % doc.group.acronym
address = '%s-ads@ietf.org' % doc.group.acronym
if address not in emails:
emails[address] = '"%s-ads"' % (doc.group.acronym)
elif doc.group.type.slug == 'rg':

View file

@ -13,6 +13,8 @@ from django.contrib.auth.decorators import login_required
from django.template.defaultfilters import pluralize
from django.contrib import messages
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State,
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS,
save_document_in_history )
@ -582,7 +584,7 @@ def to_iesg(request,name):
doc.save()
extra = {}
extra['Cc'] = "%s-chairs@tools.ietf.org, iesg-secretary@ietf.org, %s" % (doc.group.acronym,doc.notify)
extra['Cc'] = "%s-chairs@ietf.org, iesg-secretary@ietf.org, %s" % (doc.group.acronym,doc.notify)
send_mail(request=request,
to = doc.ad.email_address(),
frm = login.formatted_email(),

View file

@ -167,14 +167,29 @@ def get_or_create_initial_charter(group, group_type):
return charter
@login_required
def submit_initial_charter(request, group_type, acronym=None):
if not can_manage_group_type(request.user, group_type):
return HttpResponseForbidden("You don't have permission to access this view")
def submit_initial_charter(request, group_type=None, acronym=None):
# This needs refactoring.
# The signature assumed you could have groups with the same name, but with different types, which we do not allow.
# Consequently, this can be called with an existing group acronym and a type
# that doesn't match the existing group type. The code below essentially ignores the group_type argument.
#
# If possible, the use of get_or_create_initial_charter should be moved
# directly into charter_submit, and this function should go away.
if acronym==None:
raise Http404
group = get_object_or_404(Group, acronym=acronym)
if not group.features.has_chartering_process:
raise Http404
# This is where we start ignoring the passed in group_type
group_type = group.type_id
if not can_manage_group_type(request.user, group_type):
return HttpResponseForbidden("You don't have permission to access this view")
if not group.charter:
group.charter = get_or_create_initial_charter(group, group_type)
group.save()

View file

@ -20,6 +20,7 @@ class GroupFeatures(object):
self.default_tab = "group_docs"
elif group.type_id in ("team",):
self.has_materials = True
self.default_tab = "group_about"
if self.has_chartering_process:
self.about_page = "group_charter"

View file

@ -189,16 +189,43 @@ def wg_charters_by_acronym(request, group_type):
{ 'groups': groups },
content_type='text/plain; charset=UTF-8')
def active_groups(request, group_type):
if group_type == "wg":
def active_groups(request, group_type=None):
if not group_type:
return active_group_types(request)
elif group_type == "wg":
return active_wgs(request)
elif group_type == "rg":
return active_rgs(request)
elif group_type == "ag":
return active_ags(request)
elif group_type == "area":
return active_areas(request)
elif group_type == "team":
return active_teams(request)
elif group_type == "dir":
return active_dirs(request)
else:
raise Http404
def active_group_types(request):
grouptypes = GroupTypeName.objects.filter(slug__in=['wg','rg','ag','team','dir','area'])
return render(request, 'group/active_groups.html', {'grouptypes':grouptypes})
def active_dirs(request):
dirs = Group.objects.filter(type="dir", state="active").order_by("name")
for group in dirs:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
group.ads = sorted(roles(group, "ad"), key=extract_last_name)
group.secretaries = sorted(roles(group, "secr"), key=extract_last_name)
return render(request, 'group/active_dirs.html', {'dirs' : dirs })
def active_teams(request):
teams = Group.objects.filter(type="team", state="active").order_by("name")
for group in teams:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
return render(request, 'group/active_teams.html', {'teams' : teams })
def active_areas(request):
areas = Group.objects.filter(type="area", state="active").order_by("name")
return render(request, 'group/active_areas.html', {'areas': areas })
@ -235,6 +262,15 @@ def active_rgs(request):
return render(request, 'group/active_rgs.html', { 'irtf': irtf, 'groups': groups })
def active_ags(request):
groups = Group.objects.filter(type="ag", state="active").order_by("acronym")
for group in groups:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
group.ads = sorted(roles(group, "ad"), key=extract_last_name)
return render(request, 'group/active_ags.html', { 'groups': groups })
def bofs(request, group_type):
groups = Group.objects.filter(type=group_type, state="bof")
return render(request, 'group/bofs.html',dict(groups=groups))

View file

@ -63,8 +63,10 @@ class GroupTests(TestCase):
make_test_data()
for group in Group.objects.filter(Q(type="wg") | Q(type="rg")):
client = Client(Accept='application/pdf')
r = client.get(urlreverse("ietf.group.info.dependencies_dot",
kwargs=dict(acronym=group.acronym)))
for url in [ urlreverse("ietf.group.info.dependencies_dot",kwargs=dict(acronym=group.acronym)),
urlreverse("ietf.group.info.dependencies_dot",kwargs=dict(acronym=group.acronym,group_type=group.type_id)),
]:
r = client.get(url)
self.assertTrue(r.status_code == 200, "Failed to receive "
"a dot dependency graph for group: %s"%group.acronym)
self.assertGreater(len(r.content), 0, "Dot dependency graph for group "
@ -74,8 +76,10 @@ class GroupTests(TestCase):
make_test_data()
for group in Group.objects.filter(Q(type="wg") | Q(type="rg")):
client = Client(Accept='application/pdf')
r = client.get(urlreverse("ietf.group.info.dependencies_pdf",
kwargs=dict(acronym=group.acronym)))
for url in [ urlreverse("ietf.group.info.dependencies_pdf",kwargs=dict(acronym=group.acronym)),
urlreverse("ietf.group.info.dependencies_pdf",kwargs=dict(acronym=group.acronym,group_type=group.type_id)),
]:
r = client.get(url)
self.assertTrue(r.status_code == 200, "Failed to receive "
"a pdf dependency graph for group: %s"%group.acronym)
self.assertGreater(len(r.content), 0, "Pdf dependency graph for group "

View file

@ -10,11 +10,12 @@ import debug # pyflakes:ignore
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from django.core.urlresolvers import NoReverseMatch
from ietf.doc.models import Document, DocAlias, DocEvent, State
from ietf.group.models import Group, GroupEvent, GroupMilestone, GroupStateTransitions, MilestoneGroupEvent
from ietf.group.utils import save_group_in_history
from ietf.name.models import DocTagName, GroupStateName
from ietf.name.models import DocTagName, GroupStateName, GroupTypeName
from ietf.person.models import Person, Email
from ietf.utils.test_utils import TestCase
from ietf.utils.mail import outbox
@ -48,11 +49,37 @@ class GroupPagesTests(TestCase):
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="rg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue('Active Research Groups' in r.content)
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="area"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue("farfut" in r.content)
self.assertTrue("Far Future (farfut)" in r.content)
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="ag"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue("Active Area Groups" in r.content)
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="dir"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue("Active Directorates" in r.content)
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="team"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue("Active Teams" in r.content)
url = urlreverse('ietf.group.info.active_groups', kwargs=dict())
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue("Directorate" in r.content)
self.assertTrue("AG" in r.content)
for slug in GroupTypeName.objects.exclude(slug__in=['wg','rg','ag','area','dir','team']).values_list('slug',flat=True):
with self.assertRaises(NoReverseMatch):
url=urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type=slug))
def test_wg_summaries(self):
draft = make_test_data()
@ -195,7 +222,10 @@ class GroupPagesTests(TestCase):
due=datetime.date.today() + datetime.timedelta(days=100))
milestone.docs.add(draft)
url = group.about_url()
for url in [group.about_url(),
urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym)),
urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym,group_type=group.type_id)),
]:
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.name in r.content)
@ -214,6 +244,10 @@ class GroupPagesTests(TestCase):
state_id="active",
)
for url in [group.about_url(),
urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym)),
urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym,group_type=group.type_id)),
]:
url = group.about_url()
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
@ -235,12 +269,16 @@ class GroupPagesTests(TestCase):
doc.set_state(State.objects.get(type="slides", slug="active"))
DocAlias.objects.create(name=doc.name, document=doc)
url = urlreverse("group_materials", kwargs={ 'acronym': group.acronym })
for url in [ urlreverse("group_materials", kwargs={ 'acronym': group.acronym }),
urlreverse("group_materials", kwargs={ 'acronym': group.acronym , 'group_type': group.type_id}),
]:
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(doc.title in r.content)
self.assertTrue(doc.name in r.content)
url = urlreverse("group_materials", kwargs={ 'acronym': group.acronym })
# try deleting the document and check it's gone
doc.set_state(State.objects.get(type="slides", slug="deleted"))
@ -475,6 +513,19 @@ class GroupEditTests(TestCase):
self.assertEqual(group.groupurl_set.all()[0].name, "MARS site")
self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev))))
def test_initial_charter(self):
make_test_data()
group = Group.objects.get(acronym="mars")
for url in [ urlreverse('ietf.group.edit.submit_initial_charter', kwargs={'acronym':group.acronym}),
urlreverse('ietf.group.edit.submit_initial_charter', kwargs={'acronym':group.acronym,'group_type':group.type_id}),
]:
login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url,follow=True)
self.assertEqual(r.status_code,200)
self.assertTrue(r.redirect_chain[0][0].endswith(urlreverse('charter_submit',kwargs={'name':group.charter.name,'option':'initcharter'})))
self.client.logout()
def test_conclude(self):
make_test_data()
@ -998,8 +1049,10 @@ expand-ames-chairs@virtual.ietf.org mars_chair@ietf
def tearDown(self):
os.unlink(self.group_alias_file.name)
def testNothing(self):
url = urlreverse('ietf.group.info.email_aliases', kwargs=dict(acronym="mars"))
def testEmailAliases(self):
for testdict in [dict(acronym="mars"),dict(acronym="mars",group_type="wg")]:
url = urlreverse('ietf.group.info.email_aliases', kwargs=testdict)
r = self.client.get(url)
self.assertTrue(all([x in r.content for x in ['mars-ads@','mars-chairs@']]))
self.assertFalse(any([x in r.content for x in ['ames-ads@','ames-chairs@']]))

View file

@ -1,36 +1,18 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from django.conf.urls import patterns
from django.conf.urls import patterns, include
urlpatterns = patterns('',
(r'^$', 'ietf.group.info.active_groups'),
(r'^groupmenu.json', 'ietf.group.ajax.group_menu_data', None, "group_menu_data"),
(r'^(?P<acronym>[a-z0-9]+).json$', 'ietf.group.ajax.group_json'),
(r'^chartering/$', 'ietf.group.info.chartering_groups'),
(r'^chartering/create/(?P<group_type>(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"),
(r'^concluded/$', 'ietf.group.info.concluded_groups'),
(r'^email-aliases/$', 'ietf.group.info.email_aliases'),
# FIXME: the things below are duplicated in urls_info.py while we
# figure out whether to serve everything from /group/<acronym>,
# need to unify these at some point
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_about', None, 'group_charter'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/edit/$', 'ietf.group.edit.edit', {'action': "edit"}, "group_edit"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/conclude/$', 'ietf.group.edit.conclude'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/about/(?P<group_type>.)?$', 'ietf.group.info.group_about', None, 'group_about'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.doc.views_material.choose_material_type'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/materials/new/(?P<doc_type>[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"),
(r'^(?P<acronym>[A-Za-z0-9._+-]+)/email-aliases/$', 'ietf.group.info.email_aliases'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')),
)

View file

@ -1,9 +1,9 @@
# Copyright The IETF Trust 2008, All Rights Reserved
from django.conf.urls import patterns
from django.conf.urls import patterns, include
from django.views.generic import RedirectView
from ietf.group import info, edit, milestones
from ietf.group import info, edit
urlpatterns = patterns('',
(r'^$', info.active_groups),
@ -19,19 +19,5 @@ urlpatterns = patterns('',
(r'^bofs/$', info.bofs),
(r'^email-aliases/$', 'ietf.group.info.email_aliases'),
(r'^bofs/create/$', edit.edit, {'action': "create"}, "bof_create"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/documents/txt/$', info.group_documents_txt),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/$', info.group_home, None, "group_home"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/documents/$', info.group_documents, None, "group_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/charter/$', info.group_about, None, 'group_charter'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/history/$', info.history),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/pdf/$', info.dependencies_pdf),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/init-charter/', edit.submit_initial_charter),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/conclude/$', edit.conclude),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "group_reset_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', edit.customize_workflow),
(r'^(?P<acronym>[A-Za-z0-9._+-]+)/email-aliases/$', 'ietf.group.info.email_aliases'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')),
)

View file

@ -0,0 +1,23 @@
from django.conf.urls import patterns
urlpatterns = patterns('',
(r'^$', 'ietf.group.info.group_home', None, "group_home"),
(r'^documents/txt/$', 'ietf.group.info.group_documents_txt'),
(r'^documents/$', 'ietf.group.info.group_documents', None, "group_docs"),
(r'^charter/$', 'ietf.group.info.group_about', None, 'group_charter'),
(r'^about/$', 'ietf.group.info.group_about', None, 'group_about'),
(r'^history/$','ietf.group.info.history'),
(r'^deps/dot/$', 'ietf.group.info.dependencies_dot'),
(r'^deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
(r'^init-charter/', 'ietf.group.edit.submit_initial_charter'),
(r'^edit/$', 'ietf.group.edit.edit', {'action': "edit"}, "group_edit"),
(r'^conclude/$', 'ietf.group.edit.conclude'),
(r'^milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"),
(r'^milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"),
(r'^milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"),
(r'^workflow/$', 'ietf.group.edit.customize_workflow'),
(r'^materials/$', 'ietf.group.info.materials', None, "group_materials"),
(r'^materials/new/$', 'ietf.doc.views_material.choose_material_type'),
(r'^materials/new/(?P<doc_type>[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"),
(r'^/email-aliases/$', 'ietf.group.info.email_aliases'),
)

View file

@ -147,7 +147,7 @@ def get_fullcc_list(draft):
emails[role.email.address] = '"%s"' % (role.person.name)
# add AD
if draft.group.type.slug == 'wg':
emails['%s-ads@tools.ietf.org' % draft.group.acronym] = '"%s-ads"' % (draft.group.acronym)
emails['%s-ads@ietf.org' % draft.group.acronym] = '"%s-ads"' % (draft.group.acronym)
elif draft.group.type.slug == 'rg':
email = draft.group.parent.role_set.filter(name='chair')[0].email
emails[email.address] = '"%s"' % (email.person.name)

View file

@ -45,7 +45,7 @@ def check_audio_files(group,meeting):
continue
room = timeslot.location.name.lower()
room = room.replace(' ','')
room = room.replace('/','')
room = room.replace('/','_')
time = timeslot.time.strftime("%Y%m%d-%H%M")
filename = 'ietf{}-{}-{}-*'.format(meeting.number,room,time)
path = os.path.join(settings.MEETING_RECORDINGS_DIR,'ietf{}'.format(meeting.number),filename)

View file

@ -6,7 +6,7 @@ def get_ad_email_list(group):
'''
emails = []
if group.type.slug == 'wg':
emails.append('%s-ads@tools.ietf.org' % group.acronym)
emails.append('%s-ads@ietf.org' % group.acronym)
elif group.type.slug == 'rg' and group.parent:
emails.append(group.parent.role_set.filter(name='chair')[0].email.address)
return emails

View file

@ -136,7 +136,8 @@ class SubmissionUploadForm(forms.Form):
if self.cleaned_data.get('xml'):
#if not self.cleaned_data.get('txt'):
xml_file = self.cleaned_data.get('xml')
tfh, tfn = tempfile.mkstemp(suffix='.xml')
name, ext = os.path.splitext(os.path.basename(xml_file.name))
tfh, tfn = tempfile.mkstemp(prefix=name+'-', suffix='.xml')
try:
# We need to write the xml file to disk in order to hand it
# over to the xml parser. XXX FIXME: investigate updating
@ -150,7 +151,19 @@ class SubmissionUploadForm(forms.Form):
self.xmltree = parser.parse()
ok, errors = self.xmltree.validate()
if not ok:
raise forms.ValidationError(errors)
# Each error has properties:
#
# message: the message text
# domain: the domain ID (see lxml.etree.ErrorDomains)
# type: the message type ID (see lxml.etree.ErrorTypes)
# level: the log level ID (see lxml.etree.ErrorLevels)
# line: the line at which the message originated (if applicable)
# column: the character column at which the message originated (if applicable)
# filename: the name of the file in which the message originated (if applicable)
raise forms.ValidationError(
[ forms.ValidationError("One or more XML validation errors occurred when processing the XML file:") ] +
[ forms.ValidationError("%s: Line %s: %s" % (xml_file.name, e.line, e.message), code="%s"%e.type) for e in errors ]
)
self.xmlroot = self.xmltree.getroot()
draftname = self.xmlroot.attrib.get('docName')
revmatch = re.search("-[0-9][0-9]$", draftname)
@ -160,22 +173,24 @@ class SubmissionUploadForm(forms.Form):
else:
self.revision = None
self.filename = draftname
self.title = self.xmlroot.find('front/title').text
self.abstract = self.xmlroot.find('front/abstract').text
self.title = self.xmlroot.findtext('front/title')
self.abstract = self.xmlroot.findtext('front/abstract')
self.author_list = []
author_info = self.xmlroot.findall('front/author')
for author in author_info:
author_dict = dict(
company = author.find('organization').text,
company = author.findtext('organization'),
last_name = author.attrib.get('surname'),
full_name = author.attrib.get('fullname'),
email = author.find('address/email').text,
email = author.findtext('address/email'),
)
self.author_list.append(author_dict)
line = "%(full_name)s <%(email)s>" % author_dict
self.authors.append(line)
except forms.ValidationError:
raise
except Exception as e:
raise forms.ValidationError("Exception: %s" % e)
raise forms.ValidationError("Exception when trying to process the XML file: %s" % e)
finally:
os.close(tfh)
os.unlink(tfn)

View file

@ -77,4 +77,4 @@ class FileParser(object):
content = self.fd.file.read(4096)
mimetype = magic.from_buffer(content, mime=True)
if not mimetype == expected:
self.parsed_info.add_error('Expected an %s file of type "%s", found one of type "%s"' % (expected, mimetype))
self.parsed_info.add_error('Expected an %s file of type "%s", found one of type "%s"' % (ext.upper(), expected, mimetype))

View file

@ -47,13 +47,13 @@ def upload_submission(request):
if form.cleaned_data['xml']:
if not form.cleaned_data['txt']:
try:
file_name['txt'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (form.filename, form.revision))
try:
pagedwriter = xml2rfc.PaginatedTextRfcWriter(form.xmltree, quiet=True)
pagedwriter.write(file_name['txt'])
file_size = os.stat(file_name['txt']).st_size
except Exception as e:
raise ValidationError("Exception: %s" % e)
raise ValidationError("Error from xml2rfc: %s" % e)
file_size = os.stat(file_name['txt']).st_size
# Some meta-information, such as the page-count, can only
# be retrieved from the generated text file. Provide a
# parsed draft object to get at that kind of information.
@ -128,6 +128,10 @@ def upload_submission(request):
form._errors["__all__"] = form.error_class(["There was a failure receiving the complete form data -- please try again."])
else:
raise
except ValidationError as e:
form = SubmissionUploadForm(request=request)
form._errors = {}
form._errors["__all__"] = form.error_class(["There was a failure converting the xml file to text -- please verify that your xml file is valid. (%s)" % e.message])
else:
form = SubmissionUploadForm(request=request)

View file

@ -1,5 +1,5 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}{% load origin %}{% origin %}
{% load ietf_filters community_tags wg_menu streams_menu %}
{% load ietf_filters community_tags wg_menu streams_menu active_groups_menu %}
{% if flavor != "top" %}
{% include "base/menu_user.html" %}
@ -14,9 +14,9 @@
<ul class="dropdown-menu" role="menu">
{% endif %}
<li><a href="{% url "ietf.group.info.active_groups" group_type="area" %}">Active areas</a></li>
<li><a href="{% url "ietf.group.info.active_groups" group_type="wg" %}">Active WGs</a></li>
<li><a href="{% url "ietf.group.info.active_groups" group_type="rg" %}">Active RGs</a></li>
<li class="dropdown-submenu group-menu"><a href="{% url "ietf.group.info.active_groups" %}">Other</a>{% active_groups_menu %} </li>
{% if flavor == "top" %}<li class="divider visible-lg-block"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header visible-lg-block"{% else %}class="nav-header hidden-nojs"{% endif %}>By area/parent</li>

View file

@ -0,0 +1,8 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}{% load origin %}{% origin %}
<ul class="dropdown-menu" role="menu">
{% for p in parents %}
<li>
<a href="{{ p.menu_url }}">Active {{ p.name }}s</a>
</li>
{% endfor %}
</ul>

View file

@ -413,7 +413,7 @@
</table>
<div class="buttonlist">
<a class="btn btn-default btn-xs" href="mailto:{{ doc.name }}@tools.ietf.org?subject=Mail%20regarding%20{{ doc.name }}"><span class="fa fa-envelope-o"></span> Email authors</a>
<a class="btn btn-default btn-xs" href="mailto:{{ doc.name }}@ietf.org?subject=Mail%20regarding%20{{ doc.name }}"><span class="fa fa-envelope-o"></span> Email authors</a>
<a class="btn btn-default btn-xs" href="{% url "ipr_search" %}?submit=draft&amp;id={{ doc.name }}" rel="nofollow"><span class="fa fa-bolt"></span> IPR {% if doc.related_ipr %} <span class="badge">{{doc.related_ipr|length}}</span>{% endif %}</a>
<a class="btn btn-default btn-xs" href="{% url "doc_references" doc.canonical_name %}" rel="nofollow"><span class="fa fa-long-arrow-left"></span> References</a>
<a class="btn btn-default btn-xs" href="{% url "doc_referenced_by" doc.canonical_name %}" rel="nofollow"><span class="fa fa-long-arrow-right"></span> Referenced by</a>

View file

@ -0,0 +1,45 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% block title %}Active Area Groups{% endblock %}
{% block content %}
{% origin %}
<h1>Active Area Groups</h1>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Team</th>
<th>Name</th>
<th>AD</th>
<th>Secretaries</th>
<th>Chairs</th>
</tr>
</thead>
<tbody>
{% for group in groups %}
<tr>
<td><a href="{% url "ietf.group.info.group_home" acronym=group.acronym %}">{{ group.acronym }}</a></td>
<td>{{ group.name }}</td>
<td>
{% for ad in group.ads %}
<a href="mailto:{{ ad.email.address }}">{{ ad.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</td>
<td>
{% for secretary in group.secretaries %}
<a href="mailto:{{ secretary.email.address }}">{{ secretary.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</td>
<td>
{% for chair in group.chairs %}
<a href="mailto:{{ chair.email.address }}">{{ chair.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -12,11 +12,11 @@
<p>When changing the area structure, the IESG can decide which members are responsible for new and changed areas, including making one sitting AD responsible for multiple areas, but the IESG can only add new members through the <a href="https://www.ietf.org/nomcom/index.html">nomcom process</a>.</p>
<p>The primary task of area management is handled by one or two Area Directors per area. An AD may be advised by one or more directorates, which are created, selected, chaired and if necessary disbanded by the AD. Directorates may be specific to an area, specific to a technology, or chartered in some other fashion.</p>
<p>The ADs for an area are jointly responsible for making sure the WGs in the area are well coordinated, that there is coverage for the technologies needed in the area, and that the challenges most important to the Internet in that area are indeed being worked on.</p>
<ul><li>A full list of active working groups, sorted by area, may be found at <a href="https://datatracker.ietf.org/wg/">https://datatracker.ietf.org/wg/</a>.</li></ul>
<ul><li>A full list of active working groups, sorted by area, may be found at <a href="{% url 'ietf.group.info.active_groups' group_type='wg'%}">{% url 'ietf.group.info.active_groups' group_type='wg' %}</a>.</li></ul>
<p>The IESG decides which areas working groups belong to. The charter of each area is listed below.</p>
{% for area in areas %}
<h2>{{area.name}} ({{area.acronym}})</h2>
<h2><a href="{%url 'ietf.group.info.active_groups' group_type='wg'%}#{{area.acronym}}">{{area.name}} ({{area.acronym}})</a></h2>
<pre class="pasted">{{area.description}}</pre>
{% endfor %}
<p>For more information about the role of the IESG in areas and working groups, please see <a href="https://www.ietf.org/rfc/rfc3710.txt">RFC 3710 ("An IESG charter")</a>, section 6 and <a href="https://www.ietf.org/rfc/rfc2418.txt">RFC 2418 ("IETF Working Group Guidelines and Procedures")</a>.</p>

View file

@ -0,0 +1,46 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% block title %}Active Directorates{% endblock %}
{% block content %}
{% origin %}
<h1>Active Directorates</h1>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Team</th>
<th>Name</th>
<th>AD</th>
<th>Secretaries</th>
<th>Chairs</th>
</tr>
</thead>
<tbody>
{% for group in dirs %}
<tr>
<td><a href="{% url "ietf.group.info.group_home" acronym=group.acronym %}">{{ group.acronym }}</a></td>
<td>{{ group.name }}</td>
<td>
{% for ad in group.ads %}
<a href="mailto:{{ ad.email.address }}">{{ ad.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</td>
<td>
{% for secretary in group.secretaries %}
<a href="mailto:{{ secretary.email.address }}">{{ secretary.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</td>
<td>
{% for chair in group.chairs %}
<a href="mailto:{{ chair.email.address }}">{{ chair.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -0,0 +1,27 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% block title %}Active Groups{% endblock %}
{% block content %}
{% origin %}
<h1>Active Groups</h1>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for typename in grouptypes %}
<tr>
<td><a href="{% url "ietf.group.info.active_groups" group_type=typename.slug%}">{{ typename.name }}</a></td>
<td>{{ typename.desc }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -0,0 +1,34 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% block title %}Active Teams{% endblock %}
{% block content %}
{% origin %}
<h1>Active Teams</h1>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Team</th>
<th>Name</th>
<th>Chairs</th>
</tr>
</thead>
<tbody>
{% for group in teams %}
<tr>
<td><a href="{% url "ietf.group.info.group_home" acronym=group.acronym %}">{{ group.acronym }}</a></td>
<td>{{ group.name }}</td>
<td>
{% for chair in group.chairs %}
<a href="mailto:{{ chair.email.address }}">{{ chair.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -90,7 +90,7 @@
<p>Please make sure that your Internet-Draft includes all of the required meta-data in the proper format.</p>
<p>If your Internet-Draft <b>does</b> include all of the required meta-data in the proper format, and if
the error(s) identified above are due to the failure of the tool to extract the meta-data correctly,
the error(s) identified below are due to the failure of the tool to extract the meta-data correctly,
then please use the "Adjust meta-data" button below, which will take you to the "Adjust screen" where
you can correct the improperly extracted meta-data. You will then be able to submit your Internet-Draft
to the Secretariat for manual posting.</p>

View file

@ -54,7 +54,7 @@ urlpatterns = patterns('',
(r'^sync/', include('ietf.sync.urls')),
(r'^stream/', include('ietf.group.urls_stream')),
(r'^templates/', include('ietf.dbtemplate.urls')),
(r'^(?P<group_type>(wg|rg|area))/', include('ietf.group.urls_info')),
(r'^(?P<group_type>(wg|rg|ag|team|dir|area))/', include('ietf.group.urls_info')),
# Redirects
(r'^(?P<path>public)/', include('ietf.redirects.urls')),

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,6 @@
# -*- conf-mode -*-
setuptools>=1.2 # Require this first, to prevent later errors
#
coverage>=3.7.1
#cssselect>=0.6.1 # for PyQuery
decorator>=3.4.0
@ -19,7 +21,6 @@ python-dateutil>=2.2
python-magic>=0.4.6
python-memcached>=1.48 # for django.core.cache.backends.memcached
pytz>=2014.7
setuptools>=1.2
six>=1.8.0
wsgiref>=0.1.2
xml2rfc>=2.5.0