diff --git a/bin/test-crawl b/bin/test-crawl index 66ae5abca..b4cea6417 100755 --- a/bin/test-crawl +++ b/bin/test-crawl @@ -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: diff --git a/changelog b/changelog index 511e27080..ff2fa5226 100644 --- a/changelog +++ b/changelog @@ -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 01 Aug 2015 14:52:37 +0000 + + ietfdb (6.2.0) ietf; urgency=medium **XML-Only Draft Submission** diff --git a/ietf/__init__.py b/ietf/__init__.py index bcac7a1da..2456a220b 100644 --- a/ietf/__init__.py +++ b/ietf/__init__.py @@ -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$" diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index 88bc553c6..6184458d8 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -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 diff --git a/ietf/doc/resources.py b/ietf/doc/resources.py index 5e595bdee..35e837c15 100644 --- a/ietf/doc/resources.py +++ b/ietf/doc/resources.py @@ -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' diff --git a/ietf/doc/templatetags/active_groups_menu.py b/ietf/doc/templatetags/active_groups_menu.py new file mode 100644 index 000000000..a9915343c --- /dev/null +++ b/ietf/doc/templatetags/active_groups_menu.py @@ -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 }) + diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index c3bf73fa3..bd0ef5c96 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -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': diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 2e358d855..8e3d4dd66 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -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(), diff --git a/ietf/group/edit.py b/ietf/group/edit.py index 390a34495..e4a40aea9 100644 --- a/ietf/group/edit.py +++ b/ietf/group/edit.py @@ -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() diff --git a/ietf/group/features.py b/ietf/group/features.py index 98b5ba2da..7e32136b8 100644 --- a/ietf/group/features.py +++ b/ietf/group/features.py @@ -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" diff --git a/ietf/group/info.py b/ietf/group/info.py index 4d36e1546..22fb13000 100644 --- a/ietf/group/info.py +++ b/ietf/group/info.py @@ -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)) diff --git a/ietf/group/tests.py b/ietf/group/tests.py index 9f783186d..36f9fdd48 100644 --- a/ietf/group/tests.py +++ b/ietf/group/tests.py @@ -63,21 +63,25 @@ 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))) - 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 " - "%s has no content"%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 " + "%s has no content"%group.acronym) def test_group_document_dependency_pdffile(self): 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))) - 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 " - "%s has no content"%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 " + "%s has no content"%group.acronym) diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py index c75286ef1..9cb180e1e 100644 --- a/ietf/group/tests_info.py +++ b/ietf/group/tests_info.py @@ -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,14 +222,17 @@ class GroupPagesTests(TestCase): due=datetime.date.today() + datetime.timedelta(days=100)) milestone.docs.add(draft) - url = group.about_url() - r = self.client.get(url) - self.assertEqual(r.status_code, 200) - self.assertTrue(group.name in r.content) - self.assertTrue(group.acronym in r.content) - self.assertTrue("This is a charter." in r.content) - self.assertTrue(milestone.desc in r.content) - self.assertTrue(milestone.docs.all()[0].name in r.content) + 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) + self.assertTrue(group.acronym in r.content) + self.assertTrue("This is a charter." in r.content) + self.assertTrue(milestone.desc in r.content) + self.assertTrue(milestone.docs.all()[0].name in r.content) def test_group_about(self): make_test_data() @@ -214,12 +244,16 @@ class GroupPagesTests(TestCase): state_id="active", ) - url = group.about_url() - r = self.client.get(url) - self.assertEqual(r.status_code, 200) - self.assertTrue(group.name in r.content) - self.assertTrue(group.acronym in r.content) - self.assertTrue(group.description in r.content) + 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) + self.assertTrue(group.name in r.content) + self.assertTrue(group.acronym in r.content) + self.assertTrue(group.description in r.content) def test_materials(self): make_test_data() @@ -235,11 +269,15 @@ 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 }) - 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) + 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,11 +1049,13 @@ 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")) - 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@']])) + 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@']])) url = urlreverse('ietf.group.info.email_aliases', kwargs=dict()) login_testing_unauthorized(self, "plain", url) diff --git a/ietf/group/urls.py b/ietf/group/urls.py index 08853bbbe..00124a3a7 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -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[a-z0-9]+).json$', 'ietf.group.ajax.group_json'), (r'^chartering/$', 'ietf.group.info.chartering_groups'), (r'^chartering/create/(?P(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/, - # need to unify these at some point - (r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"), - (r'^(?P[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"), - (r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_about', None, 'group_charter'), - (r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'), - (r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'), - (r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'), - (r'^(?P[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'), - (r'^(?P[a-zA-Z0-9-._]+)/edit/$', 'ietf.group.edit.edit', {'action': "edit"}, "group_edit"), - (r'^(?P[a-zA-Z0-9-._]+)/conclude/$', 'ietf.group.edit.conclude'), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'), - (r'^(?P[a-zA-Z0-9-._]+)/about/(?P.)?$', 'ietf.group.info.group_about', None, 'group_about'), - (r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"), - (r'^(?P[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.doc.views_material.choose_material_type'), - (r'^(?P[a-zA-Z0-9-._]+)/materials/new/(?P[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"), - (r'^(?P[A-Za-z0-9._+-]+)/email-aliases/$', 'ietf.group.info.email_aliases'), + (r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"), + (r'^(?P[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')), ) diff --git a/ietf/group/urls_info.py b/ietf/group/urls_info.py index dea946c16..581509d8d 100644 --- a/ietf/group/urls_info.py +++ b/ietf/group/urls_info.py @@ -1,12 +1,12 @@ # 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), + (r'^$', info.active_groups), (r'^summary.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')), (r'^summary-by-area.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')), (r'^summary-by-acronym.txt', RedirectView.as_view(url='/wg/1wg-summary-by-acronym.txt')), @@ -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[a-zA-Z0-9-._]+)/documents/txt/$', info.group_documents_txt), - (r'^(?P[a-zA-Z0-9-._]+)/$', info.group_home, None, "group_home"), - (r'^(?P[a-zA-Z0-9-._]+)/documents/$', info.group_documents, None, "group_docs"), - (r'^(?P[a-zA-Z0-9-._]+)/charter/$', info.group_about, None, 'group_charter'), - (r'^(?P[a-zA-Z0-9-._]+)/history/$', info.history), - (r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot), - (r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', info.dependencies_pdf), - (r'^(?P[a-zA-Z0-9-._]+)/init-charter/', edit.submit_initial_charter), - (r'^(?P[a-zA-Z0-9-._]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"), - (r'^(?P[a-zA-Z0-9-._]+)/conclude/$', edit.conclude), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "group_reset_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/workflow/$', edit.customize_workflow), - (r'^(?P[A-Za-z0-9._+-]+)/email-aliases/$', 'ietf.group.info.email_aliases'), + (r'^(?P[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')), ) diff --git a/ietf/group/urls_info_details.py b/ietf/group/urls_info_details.py new file mode 100644 index 000000000..430c09b39 --- /dev/null +++ b/ietf/group/urls_info_details.py @@ -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[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"), + (r'^/email-aliases/$', 'ietf.group.info.email_aliases'), +) diff --git a/ietf/secr/drafts/email.py b/ietf/secr/drafts/email.py index a06b3aa62..d3a506401 100644 --- a/ietf/secr/drafts/email.py +++ b/ietf/secr/drafts/email.py @@ -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) diff --git a/ietf/secr/proceedings/proc_utils.py b/ietf/secr/proceedings/proc_utils.py index ad5ef03c1..eac5c17ef 100644 --- a/ietf/secr/proceedings/proc_utils.py +++ b/ietf/secr/proceedings/proc_utils.py @@ -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) diff --git a/ietf/secr/utils/mail.py b/ietf/secr/utils/mail.py index 01133dd27..386622ca6 100644 --- a/ietf/secr/utils/mail.py +++ b/ietf/secr/utils/mail.py @@ -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 diff --git a/ietf/submit/forms.py b/ietf/submit/forms.py index b59b6104f..dfb41a2e3 100644 --- a/ietf/submit/forms.py +++ b/ietf/submit/forms.py @@ -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) diff --git a/ietf/submit/parsers/base.py b/ietf/submit/parsers/base.py index 0d423d321..8dc405507 100644 --- a/ietf/submit/parsers/base.py +++ b/ietf/submit/parsers/base.py @@ -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)) diff --git a/ietf/submit/views.py b/ietf/submit/views.py index 8cd73e9a7..81c9af0f5 100644 --- a/ietf/submit/views.py +++ b/ietf/submit/views.py @@ -47,13 +47,13 @@ def upload_submission(request): if form.cleaned_data['xml']: if not form.cleaned_data['txt']: + file_name['txt'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (form.filename, form.revision)) try: - file_name['txt'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (form.filename, form.revision)) 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) diff --git a/ietf/templates/base/menu.html b/ietf/templates/base/menu.html index 60eff279b..1a6834e83 100644 --- a/ietf/templates/base/menu.html +++ b/ietf/templates/base/menu.html @@ -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 @@