From 0e2df84f21d05a1a58ed5d99db4e213458396c89 Mon Sep 17 00:00:00 2001 From: Eric Vyncke Date: Wed, 23 Jul 2025 15:58:56 +0200 Subject: [PATCH 1/5] feat: Sort IESG and IAB Statements Pages with Active Statements at the Top (#9198) * Use statement.state to sort entries * remove spurious tabs * Uncomment the URL --------- Co-authored-by: Robert Sparks --- ietf/group/views.py | 2 +- ietf/templates/group/statements.html | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ietf/group/views.py b/ietf/group/views.py index f59b270a08..0c89302c6a 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -2201,7 +2201,7 @@ def statements(request, acronym, group_type=None): ).values_list("state__slug", flat=True)[:1] ) ) - .order_by("-published") + .order_by("status", "-published") ) return render( request, diff --git a/ietf/templates/group/statements.html b/ietf/templates/group/statements.html index 035c3bc967..6bbe3cb394 100644 --- a/ietf/templates/group/statements.html +++ b/ietf/templates/group/statements.html @@ -12,8 +12,8 @@

{{group.acronym|upper}} Statements

{% if request.user|has_role:"Secretariat" %} @@ -25,16 +25,25 @@

{{group.acronym|upper}} Statements

Statement +{% regroup statements by status as grouped_statements %} +{% for statement_group in grouped_statements %} - {% for statement in statements %} + + + {{ statement_group.grouper|title }} {{"Statement"|plural:statement_group.list }} ({{ statement_group.list|length }} {{"hit"|plural:statement_group.list }}) + + + + + {% for statement in statement_group.list %} {{ statement.published|date:"Y-m-d" }} {{statement.title}} - {% if statement.status == "replaced" %}Replaced{% endif %} - {% endfor %} + {% endfor %} +{% endfor %} {% endblock %} {% block js %} From 5a862b2022cbb4650596f8a2074b273b74b04660 Mon Sep 17 00:00:00 2001 From: Rich Salz Date: Wed, 23 Jul 2025 10:01:48 -0400 Subject: [PATCH 2/5] feat: Append ascii name if any 8bit UTF8 chars (#9173) Fixes: 7167 --- ietf/utils/tests.py | 8 ++++++++ ietf/utils/xmldraft.py | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 872aa366b9..88484000f7 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -709,6 +709,14 @@ def test_render_author_name(self): )), "Joanna Q. Public", ) + self.assertEqual( + XMLDraft.render_author_name(lxml.etree.Element( + "author", + fullname=chr(340)+"ich", + asciiFullname="Rich UTF-8", + )), + chr(340)+"ich (Rich UTF-8)", + ) self.assertEqual( XMLDraft.render_author_name(lxml.etree.Element( "author", diff --git a/ietf/utils/xmldraft.py b/ietf/utils/xmldraft.py index 7ef6605c78..f555a0a16a 100644 --- a/ietf/utils/xmldraft.py +++ b/ietf/utils/xmldraft.py @@ -233,6 +233,12 @@ def render_author_name(author_elt): # Use fullname attribute, if present fullname = author_elt.attrib.get("fullname", "").strip() if fullname: + # If any 8bit chars in the fullname, try to append the author's + # name in ascii. + if any([x >= 0x80 for x in fullname.encode('utf8')]): + asciifullname = author_elt.attrib.get("asciiFullname", "").strip() + if asciifullname: + fullname = fullname + ' (' + asciifullname + ')' return fullname surname = author_elt.attrib.get("surname", "").strip() initials = author_elt.attrib.get("initials", "").strip() From 47afc6ed95895f2f7d03947813fec48417b5c2d6 Mon Sep 17 00:00:00 2001 From: Nicolas Giard Date: Wed, 23 Jul 2025 10:08:52 -0400 Subject: [PATCH 3/5] fix: prevent navigation on doc search header sort click (#9227) --- ietf/static/js/list.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/static/js/list.js b/ietf/static/js/list.js index c16111ba63..756a75001a 100644 --- a/ietf/static/js/list.js +++ b/ietf/static/js/list.js @@ -247,7 +247,8 @@ $(document) $(table) .find(".sort") - .on("click", function () { + .on("click", function (ev) { + ev.preventDefault() var order = $(this) .hasClass("asc") ? "desc" : "asc"; $.each(list_instance, (_, e) => { From ced120fd0e5ffc713b910054087149d9c6fe8933 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 23 Jul 2025 16:11:58 +0200 Subject: [PATCH 4/5] feat: improve template coverage cfg (#9230) * feat: template coverage ignores using path * refactor: reduce repeatitive redundancy * chore: copyrights * refactor: use pathlib to compute BASE_DIR * feat: settings.PROJECT_DIR * fix: glob from PROJECT_DIR not BASE_DIR * chore: remove reference code --- ietf/settings.py | 22 +++++++++++++-------- ietf/utils/test_runner.py | 41 ++++++++++++++++++++------------------- ietf/utils/tests.py | 9 ++------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index d76a8749b2..64679ca1d8 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2007-2024, All Rights Reserved +# Copyright The IETF Trust 2007-2025, All Rights Reserved # -*- coding: utf-8 -*- @@ -9,6 +9,7 @@ import os import sys import datetime +import pathlib import warnings from hashlib import sha384 from typing import Any, Dict, List, Tuple # pyflakes:ignore @@ -27,8 +28,12 @@ warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated", module="bleach") warnings.filterwarnings("ignore", message="HTTPResponse.getheader\\(\\) is deprecated", module='selenium.webdriver') -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(os.path.abspath(BASE_DIR + "/..")) +base_path = pathlib.Path(__file__).resolve().parent +BASE_DIR = str(base_path) + +project_path = base_path.parent +PROJECT_DIR = str(project_path) +sys.path.append(PROJECT_DIR) from ietf import __version__ import debug @@ -717,12 +722,13 @@ def skip_unreadable_post(record): ] # These are filename globs. They are used by test_parse_templates() and -# get_template_paths() +# get_template_paths(). Globs are applied via pathlib.Path().match, using +# the path to the template from the project root. TEST_TEMPLATE_IGNORE = [ - ".*", # dot-files - "*~", # tilde temp-files - "#*", # files beginning with a hashmark - "500.html" # isn't loaded by regular loader, but checked by test_500_page() + ".*", # dot-files + "*~", # tilde temp-files + "#*", # files beginning with a hashmark + "500.html", # isn't loaded by regular loader, but checked by test_500_page() ] TEST_COVERAGE_MAIN_FILE = os.path.join(BASE_DIR, "../release-coverage.json") diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index bfe5a56597..d0a5496283 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2009-2020, All Rights Reserved +# Copyright The IETF Trust 2009-2025, All Rights Reserved # -*- coding: utf-8 -*- # # Portion Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). @@ -54,7 +54,6 @@ import urllib3 import warnings -from fnmatch import fnmatch from typing import Callable, Optional from urllib.parse import urlencode @@ -414,8 +413,9 @@ def do_append(res, p0, p1, item): res.append((str(item.pattern), item)) return res + _all_templates = None -def get_template_paths(apps=None): +def get_template_paths(apps=None) -> list[str]: global _all_templates if not _all_templates: # TODO: Add app templates to the full list, if we are using @@ -424,25 +424,26 @@ def get_template_paths(apps=None): templatepaths = settings.TEMPLATES[0]['DIRS'] for templatepath in templatepaths: for dirpath, dirs, files in os.walk(templatepath): - if ".svn" in dirs: - dirs.remove(".svn") - relative_path = dirpath[len(templatepath)+1:] - for file in files: - ignore = False - for pattern in settings.TEST_TEMPLATE_IGNORE: - if fnmatch(file, pattern): - ignore = True - break - if ignore: - continue - if relative_path != "": - file = os.path.join(relative_path, file) - templates.add(file) - if apps: - templates = [ t for t in templates if t.split(os.path.sep)[0] in apps ] - _all_templates = templates + # glob against path from PROJECT_DIR + project_path = pathlib.Path( + dirpath.removeprefix(settings.PROJECT_DIR).lstrip("/") + ) + # label entries with name relative to templatepath + relative_path = pathlib.Path( + dirpath.removeprefix(templatepath).lstrip("/") + ) + if apps and relative_path.parts[0] not in apps: + continue # skip uninteresting apps + for filename in files: + file_path = project_path / filename + if not any( + file_path.match(pat) for pat in settings.TEST_TEMPLATE_IGNORE + ): + templates.add(relative_path / filename) + _all_templates = [str(t) for t in templates] return _all_templates + def save_test_results(failures, test_labels): # Record the test result in a file, in order to be able to check the # results and avoid re-running tests if we've already run them with OK diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 88484000f7..ce1842236d 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -19,7 +19,6 @@ from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from fnmatch import fnmatch from importlib import import_module from textwrap import dedent from tempfile import mkdtemp @@ -320,7 +319,7 @@ class TemplateChecksTestCase(TestCase): def setUp(self): super().setUp() set_coverage_checking(False) - self.paths = list(get_template_paths()) + self.paths = get_template_paths() # already filtered ignores self.paths.sort() for path in self.paths: try: @@ -335,11 +334,7 @@ def tearDown(self): def test_parse_templates(self): errors = [] for path in self.paths: - for pattern in settings.TEST_TEMPLATE_IGNORE: - if fnmatch(path, pattern): - continue - if not path in self.templates: - + if path not in self.templates: try: get_template(path) except Exception as e: From ae5080bb8d72d5e52534caf5b64d50dfda960730 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 23 Jul 2025 16:38:37 +0200 Subject: [PATCH 5/5] fix: better wrap of nomcom key in session (#9233) --- ietf/nomcom/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 10494d323f..dd651c2941 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -184,7 +184,7 @@ def retrieve_nomcom_private_key(request, year): if not private_key: return private_key - command = "%s bf -d -in /dev/stdin -k \"%s\" -a" + command = "%s aes-128-ecb -d -in /dev/stdin -k \"%s\" -a -iter 1000" code, out, error = pipe( command % ( settings.OPENSSL_COMMAND, @@ -208,7 +208,7 @@ def store_nomcom_private_key(request, year, private_key): if not private_key: request.session['NOMCOM_PRIVATE_KEY_%s' % year] = '' else: - command = "%s bf -e -in /dev/stdin -k \"%s\" -a" + command = "%s aes-128-ecb -e -in /dev/stdin -k \"%s\" -a -iter 1000" code, out, error = pipe( command % ( settings.OPENSSL_COMMAND,