From 97220a50da7efbcd090a6833da1fb981904e3ad9 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 22 Jun 2023 12:53:35 -0500 Subject: [PATCH 01/19] feat: infrastructure for statements doctype --- ietf/doc/migrations/0005_statements.py | 30 +++++++++++++++++++++++++ ietf/name/migrations/0004_statements.py | 21 +++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 ietf/doc/migrations/0005_statements.py create mode 100644 ietf/name/migrations/0004_statements.py diff --git a/ietf/doc/migrations/0005_statements.py b/ietf/doc/migrations/0005_statements.py new file mode 100644 index 00000000000..eea42d7eccd --- /dev/null +++ b/ietf/doc/migrations/0005_statements.py @@ -0,0 +1,30 @@ +# Copyright The IETF Trust 2023, All Rights Reserved + +from django.db import migrations + +def forward(apps, schema_editor): + StateType = apps.get_model("doc", "StateType") + State = apps.get_model("doc", "State") + + StateType.objects.create(slug="statement", label="Statement State") + State.objects.create(slug="active", type_id="statement", name="Active", order=0, desc="The statement is active") + State.objects.create(slug="replaced", type_id="statement", name="Active", order=0, desc="The statement has been replaced") + + +def reverse(apps, schema_editor): + StateType = apps.get_model("doc", "StateType") + State = apps.get_model("doc", "State") + + State.objects.filter(type_id="statement").delete() + StateType.objects.filter(slug="statement").delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("doc", "0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more"), + ("name", "0004_statements"), + ] + + operations = [ + migrations.RunPython(forward, reverse), + ] diff --git a/ietf/name/migrations/0004_statements.py b/ietf/name/migrations/0004_statements.py new file mode 100644 index 00000000000..ea37799746c --- /dev/null +++ b/ietf/name/migrations/0004_statements.py @@ -0,0 +1,21 @@ +# Copyright The IETF Trust 2023, All Rights Reserved + +from django.db import migrations + +def forward(apps, schema_editor): + DocTypeName = apps.get_model("name", "DocTypeName") + DocTypeName.objects.create(slug="statement", name="Statement", prefix="statement", desc="", used=True) + + +def reverse(apps, schema_editor): + DocTypeName = apps.get_model("name", "DocTypeName") + DocTypeName.objects.filter(slug="statement").delete() + +class Migration(migrations.Migration): + dependencies = [ + ("name", "0003_populate_telechatagendasectionname"), + ] + + operations = [ + migrations.RunPython(forward, reverse), + ] From a45186e5f9f6f9de07c0c99861eb22e4dd40cab4 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 22 Jun 2023 16:39:32 -0500 Subject: [PATCH 02/19] chore: basic test framework --- ietf/doc/factories.py | 19 +++++++++++- ietf/doc/tests_statement.py | 12 ++++++++ ietf/doc/views_statement.py | 1 + ietf/name/fixtures/names.json | 54 +++++++++++++++++++++++++++++++---- 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 ietf/doc/tests_statement.py create mode 100644 ietf/doc/views_statement.py diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index 95fbedfaaed..677a152299e 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -577,4 +577,21 @@ def states(obj, create, extracted, **kwargs): def reset_canonical_name(obj, create, extracted, **kwargs): if hasattr(obj, '_canonical_name'): del obj._canonical_name - return None + return None + +class StatementFactory(BaseDocumentFactory): + type_id = 'statement' + title = factory.Faker('sentence') + group = factory.SubFactory('ietf.group.factories.GroupFactory', acronym='iab') + + name = factory.LazyAttribute(lambda o: 'statement-%s-%s'%(xslugify(o.group.acronym), xslugify(o.title))) + + @factory.post_generation + def states(obj, create, extracted, **kwargs): + if not create: + return + if extracted: + for (state_type_id,state_slug) in extracted: + obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug)) + else: + obj.set_state(State.objects.get(type_id='statement',slug='active')) diff --git a/ietf/doc/tests_statement.py b/ietf/doc/tests_statement.py new file mode 100644 index 00000000000..b4c50fb2d02 --- /dev/null +++ b/ietf/doc/tests_statement.py @@ -0,0 +1,12 @@ +# Copyright The IETF Trust 2023, All Rights Reserved + +import debug # pyflakes:ignore +from ietf.doc.factories import StatementFactory +from ietf.utils.test_utils import TestCase + + +class StatementsTestCase(TestCase): + def test_statement(self): + stmt = StatementFactory() + debug.show("stmt") + debug.show("stmt.states.all()") diff --git a/ietf/doc/views_statement.py b/ietf/doc/views_statement.py new file mode 100644 index 00000000000..00f62bebbd5 --- /dev/null +++ b/ietf/doc/views_statement.py @@ -0,0 +1 @@ +# Copyright The IETF Trust 2023, All Rights Reserved diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index 1fbdfa5e9c5..ce8696aeddd 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -2539,6 +2539,32 @@ "model": "doc.state", "pk": 174 }, + { + "fields": { + "desc": "The statement is active", + "name": "Active", + "next_states": [], + "order": 0, + "slug": "active", + "type": "statement", + "used": true + }, + "model": "doc.state", + "pk": 175 + }, + { + "fields": { + "desc": "The statement has been replaced", + "name": "Active", + "next_states": [], + "order": 0, + "slug": "replaced", + "type": "statement", + "used": true + }, + "model": "doc.state", + "pk": 176 + }, { "fields": { "label": "State" @@ -2742,6 +2768,13 @@ "model": "doc.statetype", "pk": "statchg" }, + { + "fields": { + "label": "Statement State" + }, + "model": "doc.statetype", + "pk": "statement" + }, { "fields": { "about_page": "ietf.group.views.group_about", @@ -10593,6 +10626,17 @@ "model": "name.doctypename", "pk": "statchg" }, + { + "fields": { + "desc": "", + "name": "Statement", + "order": 0, + "prefix": "statement", + "used": true + }, + "model": "name.doctypename", + "pk": "statement" + }, { "fields": { "desc": "", @@ -16385,7 +16429,7 @@ "fields": { "command": "xym", "switch": "--version", - "time": "2023-05-14T07:09:32.713Z", + "time": "2023-06-21T07:09:38.578Z", "used": true, "version": "xym 0.7.0" }, @@ -16396,7 +16440,7 @@ "fields": { "command": "pyang", "switch": "--version", - "time": "2023-05-14T07:09:33.045Z", + "time": "2023-06-21T07:09:38.928Z", "used": true, "version": "pyang 2.5.3" }, @@ -16407,7 +16451,7 @@ "fields": { "command": "yanglint", "switch": "--version", - "time": "2023-05-14T07:09:33.065Z", + "time": "2023-06-21T07:09:38.948Z", "used": true, "version": "yanglint SO 1.9.2" }, @@ -16418,9 +16462,9 @@ "fields": { "command": "xml2rfc", "switch": "--version", - "time": "2023-05-14T07:09:33.970Z", + "time": "2023-06-21T07:09:39.903Z", "used": true, - "version": "xml2rfc 3.17.1" + "version": "xml2rfc 3.17.3" }, "model": "utils.versioninfo", "pk": 4 From 80035dd07b4a02f7f875c72846688c9acf9db900 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 23 Jun 2023 09:44:32 -0500 Subject: [PATCH 03/19] feat: basic statement document view --- docker/configs/settings_local.py | 2 +- ietf/doc/factories.py | 1 + ietf/doc/models.py | 2 +- ietf/doc/tests_statement.py | 40 ++++++- ietf/doc/views_doc.py | 12 +++ ietf/settings.py | 1 - ietf/templates/doc/document_statement.html | 116 +++++++++++++++++++++ 7 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 ietf/templates/doc/document_statement.html diff --git a/docker/configs/settings_local.py b/docker/configs/settings_local.py index 647fcd5b227..8178db1effa 100644 --- a/docker/configs/settings_local.py +++ b/docker/configs/settings_local.py @@ -40,7 +40,7 @@ # 'ietf.context_processors.sql_debug', # ] -DOCUMENT_PATH_PATTERN = '/assets/ietf-ftp/{doc.type_id}/' +DOCUMENT_PATH_PATTERN = '/assets/ietfdata/doc/{doc.type_id}/' INTERNET_DRAFT_PATH = '/assets/ietf-ftp/internet-drafts/' RFC_PATH = '/assets/ietf-ftp/rfc/' CHARTER_PATH = '/assets/ietf-ftp/charter/' diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index 677a152299e..088dfc9ea9f 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -585,6 +585,7 @@ class StatementFactory(BaseDocumentFactory): group = factory.SubFactory('ietf.group.factories.GroupFactory', acronym='iab') name = factory.LazyAttribute(lambda o: 'statement-%s-%s'%(xslugify(o.group.acronym), xslugify(o.title))) + uploaded_filename = factory.LazyAttribute(lambda o: f"{o.name}-{o.rev}.md") @factory.post_generation def states(obj, create, extracted, **kwargs): diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 86d814d7372..788430914b4 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -162,7 +162,7 @@ def get_file_path(self): self._cached_file_path = settings.CONFLICT_REVIEW_PATH elif self.type_id == "statchg": self._cached_file_path = settings.STATUS_CHANGE_PATH - elif self.type_id == "bofreq": + elif self.type_id == "bofreq": # TODO: This is probably unneeded, as is the separate path setting self._cached_file_path = settings.BOFREQ_PATH else: self._cached_file_path = settings.DOCUMENT_PATH_PATTERN.format(doc=self) diff --git a/ietf/doc/tests_statement.py b/ietf/doc/tests_statement.py index b4c50fb2d02..19b39461460 100644 --- a/ietf/doc/tests_statement.py +++ b/ietf/doc/tests_statement.py @@ -1,12 +1,42 @@ # Copyright The IETF Trust 2023, All Rights Reserved -import debug # pyflakes:ignore +import debug # pyflakes:ignore + +from pyquery import PyQuery + +from pathlib import Path + +from django.conf import settings +from django.urls import reverse as urlreverse + from ietf.doc.factories import StatementFactory from ietf.utils.test_utils import TestCase class StatementsTestCase(TestCase): - def test_statement(self): - stmt = StatementFactory() - debug.show("stmt") - debug.show("stmt.states.all()") + settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['DOCUMENT_PATH_PATTERN'] + + def write_statement_file(self, statement): + ( + Path(settings.DOCUMENT_PATH_PATTERN.format(doc=statement)) + / ("%s-%s.md" % (statement.name, statement.rev)) + ).write_text( + """# This is a test bofreq. +Version: {bofreq.rev} + +## A section + +This test section has some text. +""" + ) + + def test_statement_doc_view(self): + doc = StatementFactory() + self.write_statement_file(doc) + url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + q = PyQuery(response.content) + self.assertEqual(q("#statement-state").text(), "Active") + self.assertEqual(q("#statement-type").text(), "IAB Statement") + self.assertIn("has some text", q(".card-body").text()) diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index b693f54e13d..9624537c9c5 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -809,7 +809,19 @@ def document_main(request, name, rev=None, document_html=False): ) ) + if doc.type_id == "statement": + content = markdown.markdown(doc.text_or_error()) + can_manage = has_role(request.user,['Secretariat']) # Add IAB or IESG as appropriate + return render(request, "doc/document_statement.html", + dict(doc=doc, + top=top, + revisions=revisions, + latest_rev=latest_rev, + content=content, + snapshot=snapshot, + can_manage=can_manage, + )) raise Http404("Document not found: %s" % (name + ("-%s"%rev if rev else ""))) diff --git a/ietf/settings.py b/ietf/settings.py index 15ded9662e9..4aceca65f56 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -668,7 +668,6 @@ def skip_unreadable_post(record): # Override this in settings_local.py if needed # *_PATH variables ends with a slash/ . -#DOCUMENT_PATH_PATTERN = '/a/www/ietf-ftp/{doc.type_id}/' DOCUMENT_PATH_PATTERN = '/a/ietfdata/doc/{doc.type_id}/' INTERNET_DRAFT_PATH = '/a/ietfdata/doc/draft/repository' INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/' diff --git a/ietf/templates/doc/document_statement.html b/ietf/templates/doc/document_statement.html new file mode 100644 index 00000000000..f89736cfb5b --- /dev/null +++ b/ietf/templates/doc/document_statement.html @@ -0,0 +1,116 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2023, All Rights Reserved #} +{% load origin %} +{% load static %} +{% load ietf_filters %} +{% load person_filters textfilters %} +{% block title %}{{ doc.title }}{% endblock %} +{% block content %} + {% origin %} + {{ top|safe }} + {% include "doc/revisions_list.html" %} +
+ {% if doc.rev != latest_rev %} +
The information below is for an older version of this statement.
+ {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + {% with doc.docextresource_set.all as resources %} + {% if resources or can_manage %} + + + + + + + {% endif %} + {% endwith %} + + + + + + + +
DocumentType + {% if doc.group %}{{doc.group.acronym|upper}} {%endif%}Statement + {% if snapshot %}Snapshot{% endif %} +
Title{{ doc.title }}
Last updated{{ doc.time|date:"Y-m-d" }}
+ State + + {% if doc.get_state %} + {{ doc.get_state.name }} + {% else %} + No document state + {% endif %} +
Additional resources + {% if can_manage %} + Edit + {% endif %} + + {% if resources %} + {% for resource in resources|dictsort:"display_name" %} + {% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %} + + {% firstof resource.display_name resource.name.name %} + +
+ {# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #} + {% else %} + {% firstof resource.display_name resource.name.name %}: {{ resource.value|escape }} +
+ {% endif %} + {% endfor %} + {% endif %} +
+ Send notices to + + {% if not snapshot %} + {% if can_manage %} + {% doc_edit_button 'ietf.doc.views_doc.edit_notify' name=doc.name %} + {% endif %} + {% endif %} + + {{ doc.notify|default:'(None)' }} +
+ +
+
+ {{ doc.name }}-{{ doc.rev }} +
+
+ {{ content }} +
+
+{% endblock %} +{% block js %} + + +{% endblock %} \ No newline at end of file From 8229dd52c52035ed490cb4a53d44aa0e38a046e1 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 23 Jun 2023 10:58:34 -0500 Subject: [PATCH 04/19] feat: show replaced statements --- ietf/doc/migrations/0005_statements.py | 2 +- ietf/doc/tests_statement.py | 21 +++++++++++++++++++++ ietf/doc/views_doc.py | 3 +++ ietf/name/fixtures/names.json | 2 +- ietf/templates/doc/document_statement.html | 4 +++- 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ietf/doc/migrations/0005_statements.py b/ietf/doc/migrations/0005_statements.py index eea42d7eccd..3d809aeaae7 100644 --- a/ietf/doc/migrations/0005_statements.py +++ b/ietf/doc/migrations/0005_statements.py @@ -8,7 +8,7 @@ def forward(apps, schema_editor): StateType.objects.create(slug="statement", label="Statement State") State.objects.create(slug="active", type_id="statement", name="Active", order=0, desc="The statement is active") - State.objects.create(slug="replaced", type_id="statement", name="Active", order=0, desc="The statement has been replaced") + State.objects.create(slug="replaced", type_id="statement", name="Replaced", order=0, desc="The statement has been replaced") def reverse(apps, schema_editor): diff --git a/ietf/doc/tests_statement.py b/ietf/doc/tests_statement.py index 19b39461460..87d02c4b071 100644 --- a/ietf/doc/tests_statement.py +++ b/ietf/doc/tests_statement.py @@ -10,6 +10,7 @@ from django.urls import reverse as urlreverse from ietf.doc.factories import StatementFactory +from ietf.doc.models import State from ietf.utils.test_utils import TestCase @@ -40,3 +41,23 @@ def test_statement_doc_view(self): self.assertEqual(q("#statement-state").text(), "Active") self.assertEqual(q("#statement-type").text(), "IAB Statement") self.assertIn("has some text", q(".card-body").text()) + + doc.set_state(State.objects.get(type_id='statement',slug='replaced')) + doc2 = StatementFactory() + doc2.relateddocument_set.create(relationship_id='replaces', target=doc.docalias.first()) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + q = PyQuery(response.content) + self.assertEqual(q("#statement-state").text(), "Replaced") + self.assertEqual(q("#statement-type").text(), "Replaced IAB Statement") + self.assertEqual(q("#statement-type").next().text(), f"Replaced by {doc2.name}") + + url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc2.name)) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + q = PyQuery(response.content) + self.assertEqual(q("#statement-type").text(), "IAB Statement") + self.assertEqual(q("#statement-type").next().text(), f"Replaces {doc.name}") + + + diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 9624537c9c5..94d911ba288 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -812,6 +812,7 @@ def document_main(request, name, rev=None, document_html=False): if doc.type_id == "statement": content = markdown.markdown(doc.text_or_error()) can_manage = has_role(request.user,['Secretariat']) # Add IAB or IESG as appropriate + interesting_relations_that, interesting_relations_that_doc = interesting_doc_relations(doc) return render(request, "doc/document_statement.html", dict(doc=doc, @@ -820,6 +821,8 @@ def document_main(request, name, rev=None, document_html=False): latest_rev=latest_rev, content=content, snapshot=snapshot, + replaces=interesting_relations_that_doc.filter(relationship="replaces"), + replaced_by=interesting_relations_that.filter(relationship="replaces"), can_manage=can_manage, )) diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index ce8696aeddd..cb63c2cdf53 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -2555,7 +2555,7 @@ { "fields": { "desc": "The statement has been replaced", - "name": "Active", + "name": "Replaced", "next_states": [], "order": 0, "slug": "replaced", diff --git a/ietf/templates/doc/document_statement.html b/ietf/templates/doc/document_statement.html index f89736cfb5b..411b7bacb31 100644 --- a/ietf/templates/doc/document_statement.html +++ b/ietf/templates/doc/document_statement.html @@ -20,8 +20,10 @@ Type - {% if doc.group %}{{doc.group.acronym|upper}} {%endif%}Statement + {% if doc.get_state.slug != "active" %}{{doc.get_state.name}} {% endif %}{% if doc.group %}{{doc.group.acronym|upper}} {%endif%}Statement {% if snapshot %}Snapshot{% endif %} + {% if replaced_by %}
Replaced by {{ replaced_by|urlize_related_source_list:False|join:", " }}
{% endif %} + {% if replaces %}
Replaces {{ replaces|urlize_related_target_list:False|join:", " }}
{% endif %} From 4b612eb6c193b3bc42632bdc340ae525cb43732e Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 23 Jun 2023 11:00:33 -0500 Subject: [PATCH 05/19] chore: black --- ietf/doc/migrations/0005_statements.py | 17 +++++++++++++++-- ietf/doc/tests_statement.py | 17 ++++++++++------- ietf/doc/views_doc.py | 2 +- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/ietf/doc/migrations/0005_statements.py b/ietf/doc/migrations/0005_statements.py index 3d809aeaae7..a91f781ecb8 100644 --- a/ietf/doc/migrations/0005_statements.py +++ b/ietf/doc/migrations/0005_statements.py @@ -2,13 +2,26 @@ from django.db import migrations + def forward(apps, schema_editor): StateType = apps.get_model("doc", "StateType") State = apps.get_model("doc", "State") StateType.objects.create(slug="statement", label="Statement State") - State.objects.create(slug="active", type_id="statement", name="Active", order=0, desc="The statement is active") - State.objects.create(slug="replaced", type_id="statement", name="Replaced", order=0, desc="The statement has been replaced") + State.objects.create( + slug="active", + type_id="statement", + name="Active", + order=0, + desc="The statement is active", + ) + State.objects.create( + slug="replaced", + type_id="statement", + name="Replaced", + order=0, + desc="The statement has been replaced", + ) def reverse(apps, schema_editor): diff --git a/ietf/doc/tests_statement.py b/ietf/doc/tests_statement.py index 87d02c4b071..b8092271d8b 100644 --- a/ietf/doc/tests_statement.py +++ b/ietf/doc/tests_statement.py @@ -15,7 +15,9 @@ class StatementsTestCase(TestCase): - settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['DOCUMENT_PATH_PATTERN'] + settings_temp_path_overrides = TestCase.settings_temp_path_overrides + [ + "DOCUMENT_PATH_PATTERN" + ] def write_statement_file(self, statement): ( @@ -42,9 +44,11 @@ def test_statement_doc_view(self): self.assertEqual(q("#statement-type").text(), "IAB Statement") self.assertIn("has some text", q(".card-body").text()) - doc.set_state(State.objects.get(type_id='statement',slug='replaced')) + doc.set_state(State.objects.get(type_id="statement", slug="replaced")) doc2 = StatementFactory() - doc2.relateddocument_set.create(relationship_id='replaces', target=doc.docalias.first()) + doc2.relateddocument_set.create( + relationship_id="replaces", target=doc.docalias.first() + ) response = self.client.get(url) self.assertEqual(response.status_code, 200) q = PyQuery(response.content) @@ -52,12 +56,11 @@ def test_statement_doc_view(self): self.assertEqual(q("#statement-type").text(), "Replaced IAB Statement") self.assertEqual(q("#statement-type").next().text(), f"Replaced by {doc2.name}") - url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc2.name)) + url = urlreverse( + "ietf.doc.views_doc.document_main", kwargs=dict(name=doc2.name) + ) response = self.client.get(url) self.assertEqual(response.status_code, 200) q = PyQuery(response.content) self.assertEqual(q("#statement-type").text(), "IAB Statement") self.assertEqual(q("#statement-type").next().text(), f"Replaces {doc.name}") - - - diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 94d911ba288..5fd22141fe3 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -811,7 +811,7 @@ def document_main(request, name, rev=None, document_html=False): if doc.type_id == "statement": content = markdown.markdown(doc.text_or_error()) - can_manage = has_role(request.user,['Secretariat']) # Add IAB or IESG as appropriate + can_manage = has_role(request.user,["Secretariat"]) # Add IAB or IESG as appropriate interesting_relations_that, interesting_relations_that_doc = interesting_doc_relations(doc) return render(request, "doc/document_statement.html", From 36e18415191657701003ece605d36c706ca4944f Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 23 Jun 2023 12:22:39 -0500 Subject: [PATCH 06/19] fix: state help for statements --- ietf/doc/views_help.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ietf/doc/views_help.py b/ietf/doc/views_help.py index 43c029ac700..d474a33192a 100644 --- a/ietf/doc/views_help.py +++ b/ietf/doc/views_help.py @@ -5,6 +5,7 @@ from ietf.name.models import DocRelationshipName, DocTagName from ietf.doc.utils import get_tags_for_stream_id +# TODO: This is haphazardly implemented. Either flesh it out or reconsider the utility of showing these in these this way. def state_help(request, type): slug, title = { "draft-iesg": ("draft-iesg", "IESG States for Internet-Drafts"), @@ -19,6 +20,7 @@ def state_help(request, type): "status-change": ("statchg", "RFC Status Change States"), "bofreq": ("bofreq", "BOF Request States"), "procmaterials": ("procmaterials", "Proceedings Materials States"), + "statement": {"statement", "Statement States"} }.get(type, (None, None)) state_type = get_object_or_404(StateType, slug=slug) From 79989d7f8c59b8f3bb34558ee780972987ec0fc9 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 23 Jun 2023 13:04:31 -0500 Subject: [PATCH 07/19] fix: cleanout non-relevant email expansions --- ietf/mailtrigger/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/mailtrigger/utils.py b/ietf/mailtrigger/utils.py index 48d91ff6aa6..996a440fd5b 100644 --- a/ietf/mailtrigger/utils.py +++ b/ietf/mailtrigger/utils.py @@ -70,7 +70,7 @@ def starts_with(prefix): relevant.add('doc_state_edited') - if not doc.type_id in ['bofreq',]: + if not doc.type_id in ['bofreq', 'statement']: relevant.update(['doc_telechat_details_changed','ballot_deferred','iesg_ballot_saved']) if doc.type_id in ['draft','statchg']: From e505299e893a33b6e8107b886b49ff9fc64e39d5 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 27 Jun 2023 11:52:04 -0500 Subject: [PATCH 08/19] feat: import iab statements, provide group statements tab --- .../commands/import_iab_statements.py | 288 ++++++++++++++++++ .../migrations/0005_alter_docevent_type.py | 86 ++++++ ...{0005_statements.py => 0006_statements.py} | 2 +- ietf/doc/models.py | 6 +- ietf/group/urls.py | 1 + ietf/group/utils.py | 2 + ietf/group/views.py | 33 +- ietf/templates/group/statements.html | 32 ++ 8 files changed, 444 insertions(+), 6 deletions(-) create mode 100644 ietf/doc/management/commands/import_iab_statements.py create mode 100644 ietf/doc/migrations/0005_alter_docevent_type.py rename ietf/doc/migrations/{0005_statements.py => 0006_statements.py} (92%) create mode 100644 ietf/templates/group/statements.html diff --git a/ietf/doc/management/commands/import_iab_statements.py b/ietf/doc/management/commands/import_iab_statements.py new file mode 100644 index 00000000000..0cd1a74cda3 --- /dev/null +++ b/ietf/doc/management/commands/import_iab_statements.py @@ -0,0 +1,288 @@ +# Copyright The IETF Trust 2023, All Rights Reserved + +import debug # pyflakes:ignore + +import csv +import datetime +import io +import os +import shutil +import subprocess +import tempfile + +from collections import defaultdict +from pathlib import Path + +from django.conf import settings +from django.core.management.base import BaseCommand + +from ietf.doc.models import Document, DocAlias, DocEvent +from ietf.utils.text import xslugify + +class Command(BaseCommand): + help = "Performs a one-time import of IAB statements" + + def handle(self, *args, **options): + tmpdir = tempfile.mkdtemp() + process = subprocess.Popen( + ["git", "clone", "https://github.com/kesara/iab-scraper.git", tmpdir], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, stderr = process.communicate() + if not Path(tmpdir).joinpath("iab_minutes", "2022-12-14.md").exists(): + print("Git clone of the iab-scraper directory did not go as expected") + print("stdout:", stdout) + print("stderr:", stderr) + print(f"Clean up {tmpdir} manually") + exit(-1) + + spreadsheet_rows = load_spreadsheet() + for index, (file_fix, date_string, title, url, _) in enumerate(spreadsheet_rows): + name = url.split("/")[6].lower() + if name.startswith("iabs"): + name = name[5:] + elif name.startswith("iab"): + name = name[4:] + if index == 1: + name += "-archive" # https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/ + if index == 100: + name = "2010-" + name # https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/ + if index == 152: + name = "2018-" + name # https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/ + docname = f"statement-iab-{xslugify(name)}" + ext = None + base_sourcename = f"{date_string}-{file_fix}" if file_fix != "" else date_string + if Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.md").exists(): + ext = "md" + elif Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.pdf").exists(): + ext="pdf" + if ext is None: + debug.show('f"Could not find {Path(tmpdir).joinpath("iab_statements", f"{base_path}.md")}"') + continue + filename = f"{docname}-00.{ext}" + # Create Document + doc = Document.objects.create( + name=docname, + type_id="statement", + title=title, + group_id=7, # The IAB group + rev="00", + uploaded_filename=filename, + ) + DocAlias.objects.create(name=doc.name).docs.add(doc) + year, month, day = [int(part) for part in date_string.split("-")] + e1 = DocEvent.objects.create( + time = datetime.datetime(year, month, day, 12, 00, tzinfo=datetime.timezone.utc), + type="published_statement", + doc=doc, + rev="00", + by_id=1, + desc="Statement published (note: The 1200Z time of day is inaccurate - the actual time of day is not known)" + ) + e2 = DocEvent.objects.create( + type="added_comment", + doc=doc, + rev="00", + by_id=1, # The "(System)" person + desc="Statement moved into datatracker from iab wordpress website", + ) + doc.save_with_history([e1, e2]) + + # Put file in place + source = Path(tmpdir).joinpath( + "iab_statements", f"{base_sourcename}.{ext}" + ) + dest = Path(settings.DOCUMENT_PATH_PATTERN.format(doc=doc)).joinpath( + filename + ) + if dest.exists(): + print(f"WARNING: {dest} already exists - not overwriting it.") + else: + os.makedirs(dest.parent, exist_ok=True) + shutil.copy(source, dest) + + + shutil.rmtree(tmpdir) + +def load_spreadsheet(): + + csv_dump= \ +'''2002-03-01,IAB RFC Publication Process Description(txt) March 2003,https://www.iab.org/documents/correspondence-reports-documents/docs2003/iab-rfc-publication-process/,deprecated +2015-01-27,IAB Statement on Identifiers and Unicode 7.0.0 (archive),https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/,deprecated +2010-02-05,Response to the EC’s RFI on Forums and Consortiums,https://www.iab.org/documents/correspondence-reports-documents/docs2010/response-to-the-ecs-rfi-on-forums-and-consortiums/,https://www.iab.org/wp-content/IAB-uploads/2011/03/2010-02-05-IAB-Response-Euro-ICT-Questionnaire.pdf +2011-03-30,IAB responds to NTIA Request for Comments on the IANA Functions,https://www.iab.org/documents/correspondence-reports-documents/2011-2/iab-responds-to-ntia-request-for-comments-on-the-iana-functions/,https://www.iab.org/wp-content/IAB-uploads/2011/04/2011-03-30-iab-iana-noi-response.pdf +2011-07-28,IAB's response to the NTIA FNOI on IANA,https://www.iab.org/documents/correspondence-reports-documents/2011-2/iabs-response-to-the-ntia-fnoi-on-iana/,https://www.iab.org/wp-content/IAB-uploads/2011/07/IANA-IAB-FNOI-2011.pdf +2011-12-16,"Questionnaire in support of the ICANN bid for the IANA function [Dec 16, 2011]",https://www.iab.org/documents/correspondence-reports-documents/2011-2/questionnaire-in-support-of-the-icann-bid-for-the-iana-function/,https://www.iab.org/wp-content/IAB-uploads/2011/12/IAB-Past-Performance-Questionnaire.pdf +2012-04-03,IETF Oversight of the IANA Protocol Parameter Function,https://www.iab.org/documents/correspondence-reports-documents/2012-2/ietf-oversight-of-the-iana-protocol-parameter-function/,https://www.iab.org/wp-content/IAB-uploads/2012/04/IETF-IANA-Oversight.pdf +2012-04-29,IETF and IAB comment on OMB Circular A-119,https://www.iab.org/documents/correspondence-reports-documents/2012-2/ietf-and-iab-comment-on-omb-circular-a-119/,https://www.iab.org/wp-content/IAB-uploads/2012/04/OMB-119.pdf +2012-05-24,IAB submits updated ICANN performance evaluation,https://www.iab.org/documents/correspondence-reports-documents/2012-2/iab-submits-updated-icann-performance-evaluation/,https://www.iab.org/wp-content/IAB-uploads/2012/05/IAB-Past-Performance-Questionnaire-FINAL.pdf +2013-07-02,Open letter to the European Commission and the European Parliament in the matter of the Transatlantic Trade and Investment Partnership (TTIP),https://www.iab.org/documents/correspondence-reports-documents/2013-2/open-letter-to-the-ec/,https://www.iab.org/wp-content/IAB-uploads/2013/07/TTIP_market_driven_standards_EU_letter.pdf +2013-05-10,Comments In the matter of Transatlantic Trade and Investment Partnership (TTIP) (USTR-2013-0019),https://www.iab.org/documents/correspondence-reports-documents/2013-2/comments-in-the-matter-of-transatlantic-trade-and-investment-partnership-ttip-ustr-2013-0019/,https://www.iab.org/wp-content/IAB-uploads/2013/07/TTIP_market_driven_standards_FINAL.pdf +2013-10-23,IAB Comments on Recommendation for Random Number Generation Using Deterministic Random Bit Generators,https://www.iab.org/documents/correspondence-reports-documents/2013-2/nist-sp-800-90a/,https://www.iab.org/wp-content/IAB-uploads/2013/10/IAB-NIST-FINAL.pdf +2014-04-07,IAB Comments on NISTIR 7977,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-comments-on-nistir-7977/,https://www.iab.org/wp-content/IAB-uploads/2014/04/IAB-NIST7977-20140407.pdf +2014-04-29,Comments to ICANN on the Transition of NTIA’s Stewardship of the IANA Functions,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-response-to-icann-iana-transition-proposal/,https://www.iab.org/wp-content/IAB-uploads/2014/04/iab-response-to-20140408-20140428a.pdf +2016-05-27,"IAB Comments to US NTIA Request for Comments, ""The Benefits, Challenges, and Potential Roles for the Government in Fostering the Advancement of the Internet of Things""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comments-to-ntia-request-for-comments-the-benefits-challenges-and-potential-roles-for-the-government/,https://www.iab.org/wp-content/IAB-uploads/2016/05/ntia-iot-20160525.pdf +2016-05-24,"IAB Chair Testifies before the United States Senate Committee on Commerce, Science, and Transportation on ""Examining the Multistakeholder Plan for Transitioning the Internet Assigned Number Authority""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-chair-statement-before-us-senate-committee-on-iana-transition/,https://www.iab.org/wp-content/IAB-uploads/2016/05/sullivan-to-senate-commerce-20160524.pdf +2018-07-16,IAB Response to NTIA Notice of Inquiry on International Internet Policy Priorities,https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-response-to-ntia-notice-of-inquiry-on-international-internet-policy-priorities-response/,https://www.iab.org/wp-content/IAB-uploads/2018/07/IAB-response-to-the-2018-NTIA-Notice-of-Inquiry.pdf +2018-09-09,Internet Architecture Board Comments on the Australian Assistance and Access Bill 2018,https://www.iab.org/documents/correspondence-reports-documents/2018-2/internet-architecture-board-comments-on-the-australian-assistance-and-access-bill-2018/,https://www.iab.org/wp-content/IAB-uploads/2018/09/IAB-Comments-on-Australian-Assistance-and-Access-Bill-2018.pdf +2023-03-03,IAB Response to the Office of the High Commissioner for Human Rights Call for Input on “The relationship between human rights and technical standard-setting processes for new and emerging digital technologies”,https://www.iab.org/documents/correspondence-reports-documents/2023-2/iab-response-to-the-ohchr-call-for-input-on-the-relationship-between-human-rights-and-technical-standard/,https://www.iab.org/wp-content/IAB-uploads/2023/03/IAB-Response-to-OHCHR-consultation.pdf +1998-12-09,"IAB Request to IANA for Delegating IPv6 Address Space, Mail Message, December 1998",https://www.iab.org/documents/correspondence-reports-documents/docs98/iab-request-to-iana-for-delegating-ipv6-address-space-mail-message-december-1998/, +1998-12-18,"1998 Statements on Cryptography, Mail Message, December 1998.",https://www.iab.org/documents/correspondence-reports-documents/docs98/1998-statements-on-cryptography/, +1999-02-22,Correspondence between Bradner and Dyson on Protocol Parameter Parameters,https://www.iab.org/documents/correspondence-reports-documents/docs99/correspondence-between-bradner-and-dyson-on-protocol-parameter-parameters/, +1999-08-13,Comment on ICANN ASO membership,https://www.iab.org/documents/correspondence-reports-documents/docs99/comment-on-icann-aso-membership/, +1999-10-19,Ad Hoc Group on Numbering,https://www.iab.org/documents/correspondence-reports-documents/docs99/ad-hoc-group-on-numbering/, +2000-05-01,"IAB Statement on Infrastructure Domain and Subdomains, May 2000.",https://www.iab.org/documents/correspondence-reports-documents/docs2000/iab-statement-on-infrastructure-domain-and-subdomains-may-2000/, +2002-05-01,"IETF and ITU-T Cooperation Arrangements, May 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/ietf-and-itu-t-cooperation-arrangements-may-2002/, +2002-05-03,"IAB replyto ENUM liaison statement, May 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/enum-response/, +2002-05-24,"Interim Approval for Internet Telephone Numbering System (ENUM) Provisioning, 24 May 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/enum-pr/, +2002-06-01,"IAB response to ICANN Evolution and Reform, June 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/icann-response/, +2002-09-01,"IAB response to ICANN Evolution and Reform Committee's Second Interim Report, September 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/icann-response-2/, +2002-10-01,"IAB response to ICANN Evolution and Reform Committee's Final Implementation Report, October 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/icann-response-3/, +2002-12-10,"IAB Response to RIRs request regarding 6bone address entries in ip6.arpa, December 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/3ffe/, +2003-01-03,"IETF Notice of Withdrawal from the Protocol Support Organization, January 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/icann-pso-notice/, +2003-01-25,"IAB Response to Verisign GRS IDN Announcement, January 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/icann-vgrs-response/, +2003-07-10,"Note: Unified Notification Protocol Considerations, July 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-07-10-iab-notification/, +2003-08-01,Open Architectural Issues in the Development of the Internet,https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-08-architectural-issues/, +2003-08-28,RFC Document editing/ queueing suggestion,https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-08-28-klensin-rfc-editor/, +2003-09-02,"IAB Chair's announcement of an Advisory Committee, September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-02-adv-committee/, +2003-09-19,"IAB Commentary: Architectural Concerns on the Use of DNS Wildcards, September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-20-dns-wildcards/, +2003-09-24,"IAB to ICANN: IAB input related to the .cs code in ISO 3166, 24 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-icann-cs-code/, +2003-09-24,"IAB to ISO: IAB comment on stability of ISO 3166 and other infrastructure standards, 24 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-iso-cs-code/, +2003-09-25,"Correspondance to ISO concerning .cs code, and advice to ICANN, 25 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-icann-cs-code-2/, +2003-09-25,"Correspondance to ISO concerning .cs code, and advice to ICANN, 25 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-iso-cs-code-2/, +2003-09-26,ISO Codes,https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-23-isocodes/, +2003-10-02,"IESG to IAB: Checking data for validity before usage in a protocol, 2 October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-02-iesg-dns-validity-check-query/, +2003-10-14,"Survey of Current Security Work in the IETF, October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-14-security-survey/, +2003-10-17,"IAB to ICANN SESAC:Wildcard entries in DNS domains, 17 October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-17-crocker-wildcards-2/, +2003-10-17,"IAB note to Steve Crocker, Chair, ICANN Security and Stability Advisory Committee, October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-17-crocker-wildcards/, +2003-10-18,"IAB concerns against permanent deployment of edge-based port filtering, October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-18-edge-filters/, +2003-11-08,"IAB Response to IESG architectural query: Checking data for validity before usage in protocol, November 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-11-08-iesg-dns-validity-check-query-response/, +2004-01-19,"Number Resource Organisation (NRO) formation, 19 January 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-01-19-nro/, +2004-01-22,"IAB to RIPE NCC:ENUM Administration, 22 January 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-01-22-enum-subcodes/, +2004-02-09,"IAB to IANA: Instructions to IANA -Delegation of 2.0.0.2.ip6.arpa, 9 February, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-02-09-6to4-rev-delegation/, +2004-02-26,The IETF -- what is it?,https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-02-26-ietf-defn/, +2004-04-15,"IAB to ICANN: Validity checks for names, 15 April, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-04-15-icann-dns-validity-check/, +2004-05-07,"IAB to IANA: IPv6 Allocation Policy , 7 May, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-05-07-iana-v6alloc/, +2004-05-24,"IAB to IANA: Instructions to IANA -Delegation of 3.f.f.e.ip6.arpa, 24 May, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-05-24-3ffe-rev-delegation/, +2004-05-27,"IAB to ICANN:Concerns regarding IANA Report, 27 May, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-05-27-iana-report/, +2004-07-16,"Upcoming clarifications to RIPE NCC instructions for e164.arpa operation, 16 July 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-07-15-enum-instructions/, +2004-07-16,"IANA Delegation Requests, 16 July 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-07-16-iana-delegation/, +2004-08-06,OMA-IETF Standardization Collaboration,https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-08-draft-iab-oma-liaison-00/, +2004-08-12,"IAB to RIPE NCC:Notice of revision of instructions concerning the ENUM Registry, 12 August, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-08-12-enum-instructions/, +2004-08-12,"Response to your letter of August 4, 12 August 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-08-12-icann-wildcard/, +2004-09-27,"IAB to ICANN:Report of Concerns over IANA Performance , 27 September, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-09-27-iana-concerns/, +2004-09-27,"IAB Report of IETF IANA Functions , 27 September 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-09-27-iana-report/, +2004-11-03,"IAB to IESG:Comments on Teredo , 3 November, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-11-03-teredo-comments/, +2004-11-12,"IAB to ICANN:Response to ICANN Request for assistance with new TLD Policy , 12 November, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-11-12-icann-new-tld-policy/, +2004-11-29,"The IETF and IPv6 Address Allocation , 29 November 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-11-29-ipv6-allocs/, +2004-12-15,"IAB Comment to Internet AD:Comments on IANA Considerations in IPv6 ULA draft, 15 December, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-12-15-ipv6-ula-iana-considerations/, +2005-02-16,"IAB review of Structure of the IETF Administrative Support Activity, 16 February 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-02-16-iasa/, +2005-08-26,"SiteFinder returns, 26 August 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-08-26-ssac-note/, +2005-09-01,"Re: SiteFinder returns, 1 September 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-09-01-ssac-response/, +2005-10-14,"IAB to ICANN: IAB comments on ICANN IDN Guidelines, 14 October 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-10-14-idn-guidelines/, +2005-11-07,"IAB to ICANN – Nameserver change for e164.arpa, 7 November 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-11-07-nameserver-change/, +2005-11-22,"IETF to ICANN – IANA structural status, 22 November 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-11-22-iana-structure/, +2005-11-29,"IAB to IANA – Teredo prefix assignment, 29 November 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-11-29-teredo-prefix/, +2005-12-22,"IAB to ICANN – dot arpa TLD management, 22 December 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-12-22-dot-arpa/, +2006-03-06,"IAB Position on the IETF IANA Technical Parameter Function, 6 March 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/iab-iana-position/, +2006-03-28,"IAB to ICANN – Name server changes for ip6.arpa, 28 March 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-03-28-nameserver-change/, +2006-04-20,"IAB to IANA – Administrative contact information change for arpa, 20 April 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-04-20-update-to-administrative-contact-information-for-arpa-iana/, +2006-04-20,"IAB to ITU TSB – FYI re contact info changes for e164.arpa, 20 April 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-04-20-update-to-contact-information-for-e164-arpa-hill/, +2006-04-20,"IAB to IANA – Contact information changes for e164.arpa, 20 April 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-04-20-update-to-contact-information-for-e164-arpa-iana/, +2006-05-15,"IAB to IANA – Request to IANA for status update on deployment of DNSSEC on IANA managed zones, 15 May 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-05-15-iab-request-to-iana-to-sign-dnssec-zones/, +2006-06-07,"The IAB announces the mailing list for the discussion of the independent submissions process, 7 June 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-06-07-independent-submissions/, +2006-06-19,"Procedural issues with liaison on nextsteps, 19 June 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-06-16-response-to-idn-liaison-issues/, +2006-10-12,"The IAB sends a note to the Registry Services Technical Evaluation Panel on the use of wildcards in the .travel TLD, 12 October 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-10-12-rstep-note/, +2006-10-19,"The IAB sends a note to the OIF Technical Committee Chair on IETF Protocol Extensions, 19 October 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-10-19-oifnote/, +2007-05-21,"The IAB responds to ITU Consultation on Resolution 102, 21 May 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/2007-05-21-itu-resolution-102/, +2007-07-05,"Correspondence from the RIPE NCC regarding deployment of DNSSEC in the E164.ARPA zone, 5 July 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/2007-07-05-ripe-ncc-dnssec-e164/, +2007-07-24,"Correspondence from the IAB to the ITU-TSB Director regarding deployment of DNSSEC in the E164.ARPA zone, 24 July 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/2007-07-24-iab-itu-dnssec-e164/, +2007-10-10,"Follow-up work on NAT-PT, 10 October 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/follow-up-work-on-nat-pt/, +2008-02-15,"Correspondence from the IAB to the National Telecommunications and Information Administration, US Department of Commerce regarding the ICANN/DoC Joint Project Agreement, 15 February 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-02-15-midterm-view-icann-doc-jpa/, +2008-03-07,"The IAB’s response to ICANN’s solicitation on DNS stability, 7 March 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-03-07-icann-new-gtlds/, +2008-06-04,Proposed RFC Editor Structure,https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-06-04-rfc-editor-model/, +2008-08-16,"The IAB’s response to Geoff Huston’s request concerning 32-bit AS numbers, 16 August 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-08-16-32bit-as-huston/, +2008-09-05,Proposed RFC Editor Structure,https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-09-05-rfc-editor-model/, +2008-11-18,"The IAB’s correspondence with NTIA on DNSSEC deployment at the root, 18 November 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-11-18-dnssec-deployment-at-the-root/, +2008-12-04,"IAB correspondence with Geoff Huston on TAs, IANA, RIRs et al.., 4 December 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-12-04-huston-tas-iana-rirs/, +2009-06-02,"IAB correspondence with IANA on the Signing of .ARPA, 2 June 2009",https://www.iab.org/documents/correspondence-reports-documents/docs2009/2009-06-02-roseman-signing-by-iana-of-arpa/, +2009-10-14,"IAB correspondence with ICANN on their “Scaling the Root” study., 14 October 2009",https://www.iab.org/documents/correspondence-reports-documents/docs2009/2009-10-14-icann-scaling-the-root/, +2010-01-27,IAB statement on the RPKI,https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/, +2010-07-30,Transition of IN-ADDR.ARPA generation,https://www.iab.org/documents/correspondence-reports-documents/docs2010/transition-of-in-addr-arpa-generation/, +2011-06-22,Response to ARIN's request for guidance regarding Draft Policy ARIN-2011-5,https://www.iab.org/documents/correspondence-reports-documents/2011-2/response-to-arins-request-for-guidance-regarding-draft-policy-arin-2011-5/, +2011-07-25,"IAB Response to ""Some IESG Thoughts on Liaisons""",https://www.iab.org/documents/correspondence-reports-documents/2011-2/iab-response-to-some-iesg-thoughts-on-liaisons/, +2011-09-16,Letter to the European Commission on Global Interoperability in Emergency Services,https://www.iab.org/documents/correspondence-reports-documents/2011-2/letter-to-the-european-commission-on-global-interoperability-in-emergency-services/, +2012-02-08,"IAB Statement: ""The interpretation of rules in the ICANN gTLD Applicant Guidebook""",https://www.iab.org/documents/correspondence-reports-documents/2012-2/iab-statement-the-interpretation-of-rules-in-the-icann-gtld-applicant-guidebook/, +2012-03-26,"Response to ICANN questions concerning ""The interpretation of rules in the ICANN gTLD Applicant Guidebook""",https://www.iab.org/documents/correspondence-reports-documents/2012-2/response-to-icann-questions-concerning-the-interpretation-of-rules-in-the-icann-gtld-applicant-guidebook/, +2012-03-30,IAB Member Roles in Evaluating New Work Proposals,https://www.iab.org/documents/correspondence-reports-documents/2012-2/iab-member-roles-in-evaluating-new-work-proposals/, +2012-08-29,Leading Global Standards Organizations Endorse ‘OpenStand’ Principles that Drive Innovation and Borderless Commerce,https://www.iab.org/documents/correspondence-reports-documents/2012-2/leading-global-standards-organizations-endorse-%e2%80%98openstand/, +2013-03-28,IAB Response to RSSAC restructure document (28 March 2013),https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-response-to-rssac-restructure-document-28-march-2013/, +2013-05-28,Consultation on Root Zone KSK Rollover from the IAB,https://www.iab.org/documents/correspondence-reports-documents/2013-2/consultation-on-root-zone-ksk-rollover-from-the-iab/, +2013-07-10,IAB Statement: Dotless Domains Considered Harmful,https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/, +2013-07-16,IAB Response to ICANN Consultation on the Source of Policies & User Instructions for Internet Number Resource Requests,https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-response-to-iana-policies-user-instructions-25jun13/, +2013-10-03,Statement from the IAB on the Strengths of the OpenStand Principles,https://www.iab.org/documents/correspondence-reports-documents/2013-2/statement-from-openstand-on-the-strengths-of-the-openstand-principles/, +2013-10-07,Montevideo Statement on the Future of Internet Cooperation,https://www.iab.org/documents/correspondence-reports-documents/2013-2/montevideo-statement-on-the-future-of-internet-cooperation/, +2013-11-27,IAB Statement on draft-farrell-perpass-attack-00,https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-on-draft-farrell-perpass-attack-00/, +2014-01-23,IAB Comments Regarding the IRTF CFRG chair,https://www.iab.org/documents/correspondence-reports-documents/2014-2/0123-iab-comments-regarding-the-irtf-cfrg-chair/, +2014-02-14,"Statement from the I* Leaders Coordination Meeting, Santa Monica, 14 February 2014",https://www.iab.org/documents/correspondence-reports-documents/2014-2/statement-from-the-i-leaders-coordination-meeting-santa-monica-14-february-2014/, +2014-03-11,Re: Guiding the Evolution of the IANA Protocol Parameter Registries,https://www.iab.org/documents/correspondence-reports-documents/2014-2/re-guiding-the-evolution-of-the-iana-protocol-parameter-registries/, +2014-03-14,Internet Technical Leaders Welcome IANA Globalization Progress,https://www.iab.org/documents/correspondence-reports-documents/2014-2/internet-technical-leaders-welcome-iana-globalization-progress/, +2014-05-13,I* Post-NETmundial Meeting Statement,https://www.iab.org/documents/correspondence-reports-documents/2014-2/i-post-netmundial-meeting-statement/, +2014-06-05,Comments on ICANN Board Member Compensation from the IAB,https://www.iab.org/documents/correspondence-reports-documents/2014-2/comments-on-icann-board-member-compensation/, +2014-11-13,IAB Statement on Internet Confidentiality,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-statement-on-internet-confidentiality/, +2014-12-04,IAB statement on the NETmundial Initiative,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-statement-on-the-netmundial-initiative/, +2014-12-17,IAB Comments on CSTD Report Mapping International Internet Public Policy Issues,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-comments-on-cstd-report-mapping-international-public-policy-issues/, +2015-02-11,IAB liaison to ICANN Root Server System Advisory Committee (RSSAC),https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-liaison-to-icann-root-server-system-advisory-council-rssac/, +2015-02-11,IAB Statement on Identifiers and Unicode 7.0.0,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/, +2015-03-02,IAB Statement on Liaison Compensation,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-liaison-compensation/, +2015-04-09,IAB Comments on The HTTPS-Only Standard,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-the-https-only-standard/, +2015-06-03,IAB comments on CCWG-Accountability Draft Report,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-ccwg-accountability-draft-report/, +2015-06-12,IAB Statement on the Trade in Security Technologies,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-the-trade-in-security-technologies/, +2015-06-24,"IAB Correspondence to U.S. Bureau of Industry and Security, re RIN 0694-AG49",https://www.iab.org/documents/correspondence-reports-documents/2015-2/rin-0694-ag49/, +2015-09-07,Internet Architecture Board comments on the ICG Proposal,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-icg-proposal/, +2015-09-09,IAB comments on the CCWG accountability 2d draft report,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-ccwg-accountability/, +2015-10-07,IAB Comments to FCC on Rules regarding Authorization of Radiofrequency Equipment,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-fcc-15-92/, +2015-12-16,IAB comments on the CCWG accountability 3d draft report,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-the-ccwg-accountability-3d-draft-report/, +2016-01-13,"Comments from the Internet Architecture Board (IAB) on ""Registration Data Access Protocol (RDAP) Operational Profile for gTLD Registries and Registrars""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/comments-from-the-internet-architecture-board-iab-on-registration-data-access-protocol-rdap-operational-profile-for-gtld-registries-and-registrars/, +2016-05-04,IAB comments on Draft New ICANN Bylaws,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comments-on-draft-new-icann-bylaws/, +2016-05-11,IAB Comments on Proposed Changes to Internet Society Bylaws,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comments-on-proposed-changes-to-internet-society-bylaws/, +2016-07-17,Comments from the IAB on LGRs for second level,https://www.iab.org/documents/correspondence-reports-documents/2016-2/comments-from-the-iab-on-lgrs-for-second-level/, +2016-09-01,IAB statement on IANA Intellectual Property Rights,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-statement-on-iana-intellectual-property-rights/, +2016-09-14,IAB Statement on the IANA Stewardship Transition,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-statement-on-the-iana-stewardship-transition/, +2016-11-07,IAB Statement on IPv6,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-statement-on-ipv6/, +2016-12-07,"IAB comment on ""Revised Proposed Implementation of GNSO Thick Whois Consensus Policy Requiring Consistent Labeling and Display of RDDS (Whois) Output for All gTLDs""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comment-on-revised-proposed-implementation-of-gnso-thick-whois-consensus-policy-requiring-consistent-labeling-and-display-of-rdds-whois-output-for-all-gtlds/, +2017-01-04,IAB comments on Identifier Technology Health Indicators: Definition,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-comments-on-identifier-technology-health-indicators-definition/, +2017-02-01,IAB Statement on OCSP Stapling,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-statement-on-ocsp-stapling/, +2017-02-16,Follow up on barriers to entry blog post,https://www.iab.org/documents/correspondence-reports-documents/2017-2/follow-up-on-barriers-to-entry-blog-post/, +2017-03-02,IAB Comments to United States NTIA on the Green Paper: Fostering the Advancement of the Internet of Things,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-comments-to-ntia-on-fostering-the-advancement-of-iot/, +2017-03-30,Internet Architecture Board statement on the registration of special use names in the ARPA domain,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-statement-on-the-registration-of-special-use-names-in-the-arpa-domain/, +2017-05-01,Comments from the IAB on IDN Implementation Guidelines,https://www.iab.org/documents/correspondence-reports-documents/2017-2/comments-from-the-iab-on-idn-implementation-guidelines/, +2017-07-31,IAB Response to FCC-17-89,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-response-to-fcc-17-89/, +2018-03-15,IAB Statement on Identifiers and Unicode,https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-identifiers-and-unicode/, +2018-04-03,IAB Statement on the RPKI,https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/, +2019-05-02,Revised Operating Instructions for e164.arpa (ENUM),https://www.iab.org/documents/correspondence-reports-documents/2019-2/revised-operating-instructions-for-e164-arpa-enum/, +2019-06-26,Comments on Evolving the Governance of the Root Server System,https://www.iab.org/documents/correspondence-reports-documents/2019-2/comments-on-evolving-the-governance-of-the-root-server-system/, +2019-09-04,Avoiding Unintended Harm to Internet Infrastructure,https://www.iab.org/documents/correspondence-reports-documents/2019-2/avoiding-unintended-harm-to-internet-infrastructure/, +2020-07-01,"IAB correspondence with the National Telecommunications and Information Administration (NTIA) on DNSSEC deployment for the Root Zone [Docket No. 100603240-0240-01], 1 July 2010",https://www.iab.org/documents/correspondence-reports-documents/docs2010/2010-07-01-alexander-dnssec-deployment-for-the-root-zone/, +2020-09-29,IAB Comments on the Draft Final Report on the new gTLD Subsequent Procedures Policy Development Process,https://www.iab.org/documents/correspondence-reports-documents/2020-2/iab-comments-on-new-gtld-subsequent-procedures/, +2021-07-14,IAB Statement on Inclusive Language in IAB Stream Documents,https://www.iab.org/documents/correspondence-reports-documents/2021-2/iab-statement-on-inclusive-language-in-iab-stream-documents/, +2022-04-08,IAB comment on Mandated Browser Root Certificates in the European Union’s eIDAS Regulation on the Internet,https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-comment-on-mandated-browser-root-certificates-in-the-european-unions-eidas-regulation-on-the-internet/, +2022-04-08,"IAB Comments on A Notice by the Federal Communications Commission on Secure Internet Routing, issued 03/11/2022",https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-comments-on-a-notice-by-the-federal-communications-commission-on-secure-internet-routing-issued-03-11-2022/, +2022-07-08,IAB Statement to OSTP on Privacy-Enhancing Technologies,https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-statement-to-ostp-on-privacy-enhancing-technologies/, +2022-11-21,IAB Comments on a notice by the Federal Trade Commission on “Trade Regulation Rule on Commercial Surveillance and Data Security” (16 CFR Part 464),https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-comments-on-a-notice-by-the-federal-trade-commission-on-trade-regulation-rule-on-commercial-surveillance-and-data-security-16-cfr-part-464/, +''' + + rows = [] + date_count = defaultdict(lambda:0) + with io.StringIO(csv_dump) as csv_file: + reader = csv.reader(csv_file) + for row in reader: + date = row[0] + if date_count[date] == 0: + row.insert(0, "") + else: + row.insert(0,date_count[date]) + date_count[date] += 1 + rows.append(row) + return rows diff --git a/ietf/doc/migrations/0005_alter_docevent_type.py b/ietf/doc/migrations/0005_alter_docevent_type.py new file mode 100644 index 00000000000..dbc7d09e976 --- /dev/null +++ b/ietf/doc/migrations/0005_alter_docevent_type.py @@ -0,0 +1,86 @@ +# Generated by Django 4.2.2 on 2023-06-27 14:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("doc", "0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="docevent", + name="type", + field=models.CharField( + choices=[ + ("new_revision", "Added new revision"), + ("new_submission", "Uploaded new revision"), + ("changed_document", "Changed document metadata"), + ("added_comment", "Added comment"), + ("added_message", "Added message"), + ("edited_authors", "Edited the documents author list"), + ("deleted", "Deleted document"), + ("changed_state", "Changed state"), + ("changed_stream", "Changed document stream"), + ("expired_document", "Expired document"), + ("extended_expiry", "Extended expiry of document"), + ("requested_resurrect", "Requested resurrect"), + ("completed_resurrect", "Completed resurrect"), + ("changed_consensus", "Changed consensus"), + ("published_rfc", "Published RFC"), + ( + "added_suggested_replaces", + "Added suggested replacement relationships", + ), + ( + "reviewed_suggested_replaces", + "Reviewed suggested replacement relationships", + ), + ("changed_action_holders", "Changed action holders for document"), + ("changed_group", "Changed group"), + ("changed_protocol_writeup", "Changed protocol writeup"), + ("changed_charter_milestone", "Changed charter milestone"), + ("initial_review", "Set initial review time"), + ("changed_review_announcement", "Changed WG Review text"), + ("changed_action_announcement", "Changed WG Action text"), + ("started_iesg_process", "Started IESG process on document"), + ("created_ballot", "Created ballot"), + ("closed_ballot", "Closed ballot"), + ("sent_ballot_announcement", "Sent ballot announcement"), + ("changed_ballot_position", "Changed ballot position"), + ("changed_ballot_approval_text", "Changed ballot approval text"), + ("changed_ballot_writeup_text", "Changed ballot writeup text"), + ("changed_rfc_editor_note_text", "Changed RFC Editor Note text"), + ("changed_last_call_text", "Changed last call text"), + ("requested_last_call", "Requested last call"), + ("sent_last_call", "Sent last call"), + ("scheduled_for_telechat", "Scheduled for telechat"), + ("iesg_approved", "IESG approved document (no problem)"), + ("iesg_disapproved", "IESG disapproved document (do not publish)"), + ("approved_in_minute", "Approved in minute"), + ("iana_review", "IANA review comment"), + ("rfc_in_iana_registry", "RFC is in IANA registry"), + ( + "rfc_editor_received_announcement", + "Announcement was received by RFC Editor", + ), + ("requested_publication", "Publication at RFC Editor requested"), + ( + "sync_from_rfc_editor", + "Received updated information from RFC Editor", + ), + ("requested_review", "Requested review"), + ("assigned_review_request", "Assigned review request"), + ("closed_review_request", "Closed review request"), + ("closed_review_assignment", "Closed review assignment"), + ("downref_approved", "Downref approved"), + ("posted_related_ipr", "Posted related IPR"), + ("removed_related_ipr", "Removed related IPR"), + ("changed_editors", "Changed BOF Request editors"), + ("published_statement", "Published statement"), + ], + max_length=50, + ), + ), + ] diff --git a/ietf/doc/migrations/0005_statements.py b/ietf/doc/migrations/0006_statements.py similarity index 92% rename from ietf/doc/migrations/0005_statements.py rename to ietf/doc/migrations/0006_statements.py index a91f781ecb8..9a074292e5c 100644 --- a/ietf/doc/migrations/0005_statements.py +++ b/ietf/doc/migrations/0006_statements.py @@ -34,7 +34,7 @@ def reverse(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("doc", "0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more"), + ("doc", "0005_alter_docevent_type"), ("name", "0004_statements"), ] diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 788430914b4..6c8fae45a24 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -1290,7 +1290,11 @@ class DocReminder(models.Model): ("removed_related_ipr", "Removed related IPR"), # Bofreq Editor events - ("changed_editors", "Changed BOF Request editors") + ("changed_editors", "Changed BOF Request editors"), + + # Statement events + ("published_statement", "Published statement"), + ] class DocEvent(models.Model): diff --git a/ietf/group/urls.py b/ietf/group/urls.py index 0e4f7ef2fb9..da9b5af7845 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -48,6 +48,7 @@ url(r'^secretarysettings/$', views.change_review_secretary_settings), url(r'^reset_next_reviewer/$', views.reset_next_reviewer), url(r'^email-aliases/$', RedirectView.as_view(pattern_name=views.email,permanent=False),name='ietf.group.urls_info_details.redirect.email'), + url(r'^statements/$', views.statements), ] diff --git a/ietf/group/utils.py b/ietf/group/utils.py index b701d6a7c79..0ad13c6b170 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -233,6 +233,8 @@ def construct_group_menu_context(request, group, selected, group_type, others): if group.features.has_meetings: entries.append(("Meetings", urlreverse("ietf.group.views.meetings", kwargs=kwargs))) + if group.acronym in ["iab", "iesg"]: + entries.append(("Statements", urlreverse("ietf.group.views.statements", kwargs=kwargs))) entries.append(("History", urlreverse("ietf.group.views.history", kwargs=kwargs))) entries.append(("Photos", urlreverse("ietf.group.views.group_photos", kwargs=kwargs))) entries.append(("Email expansions", urlreverse("ietf.group.views.email", kwargs=kwargs))) diff --git a/ietf/group/views.py b/ietf/group/views.py index 88b25a1091e..1c4ac316054 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright The IETF Trust 2009-2022, All Rights Reserved +# Copyright The IETF Trust 2009-2023, All Rights Reserved # # Portion Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). # All rights reserved. Contact: Pasi Eronen @@ -48,7 +48,7 @@ from django import forms from django.conf import settings from django.contrib.auth.decorators import login_required -from django.db.models import Q, Count +from django.db.models import Q, Count, OuterRef, Subquery from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse from django.shortcuts import render, redirect, get_object_or_404 from django.template.loader import render_to_string @@ -61,7 +61,7 @@ from ietf.community.models import CommunityList, EmailSubscription from ietf.community.utils import docs_tracked_by_community_list -from ietf.doc.models import DocTagName, State, DocAlias, RelatedDocument, Document +from ietf.doc.models import DocTagName, State, DocAlias, RelatedDocument, Document, DocEvent from ietf.doc.templatetags.ietf_filters import clean_whitespace from ietf.doc.utils import get_chartering_type, get_tags_for_stream_id from ietf.doc.utils_charter import charter_name_for_group, replace_charter_of_replaced_group @@ -2093,7 +2093,32 @@ def reset_next_reviewer(request, acronym, group_type=None): return render(request, 'group/reset_next_reviewer.html', { 'group':group, 'form': form,}) - +def statements(request, acronym, group_type=None): + if not acronym in ["iab", "iesg"]: + raise Http404 + group = get_group_or_404(acronym, group_type) + statements = group.document_set.filter(type_id="statement").annotate( + published=Subquery( + DocEvent.objects.filter( + doc=OuterRef("pk"), + type="published_statement" + ).order_by("-time").values("time")[:1] + ) + ).order_by("-published") + return render( + request, + "group/statements.html", + construct_group_menu_context( + request, + group, + "statements", + group_type, + { + "group": group, + "statements": statements, + }, + ), + ) diff --git a/ietf/templates/group/statements.html b/ietf/templates/group/statements.html new file mode 100644 index 00000000000..5e57fdee803 --- /dev/null +++ b/ietf/templates/group/statements.html @@ -0,0 +1,32 @@ +{% extends "group/group_base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} +{% load ietf_filters person_filters textfilters %} +{% load static %} +{% block pagehead %} + +{% endblock %} +{% block group_content %} + {% origin %} +

{{group.acronym|upper}} Statements

+ {% origin %} + + + + + + + + + {% for statement in statements %} + + + + + {% endfor %} + +
DateStatement
{{ statement.published|date:"Y-m-d" }}{{statement.title}}
+{% endblock %} +{% block js %} + +{% endblock %} \ No newline at end of file From 3417ad590dc34a431fc1ca929996dca6843c282e Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 27 Jun 2023 11:57:25 -0500 Subject: [PATCH 09/19] fix: guard against running import twice --- ietf/doc/management/commands/import_iab_statements.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ietf/doc/management/commands/import_iab_statements.py b/ietf/doc/management/commands/import_iab_statements.py index 0cd1a74cda3..95eeb6cbac3 100644 --- a/ietf/doc/management/commands/import_iab_statements.py +++ b/ietf/doc/management/commands/import_iab_statements.py @@ -23,6 +23,9 @@ class Command(BaseCommand): help = "Performs a one-time import of IAB statements" def handle(self, *args, **options): + if Document.objects.filter(type="statement", group__acronym="iab").exists(): + print("IAB statement documents already exist - exiting") + exit(-1) tmpdir = tempfile.mkdtemp() process = subprocess.Popen( ["git", "clone", "https://github.com/kesara/iab-scraper.git", tmpdir], From 78c92f55eee1b96279db78c1303cc28ed936f96a Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 27 Jun 2023 13:26:17 -0500 Subject: [PATCH 10/19] feat: build redirect csv for iab statements --- .../commands/import_iab_statements.py | 133 ++++++++++-------- 1 file changed, 71 insertions(+), 62 deletions(-) diff --git a/ietf/doc/management/commands/import_iab_statements.py b/ietf/doc/management/commands/import_iab_statements.py index 95eeb6cbac3..5e169a9adce 100644 --- a/ietf/doc/management/commands/import_iab_statements.py +++ b/ietf/doc/management/commands/import_iab_statements.py @@ -41,69 +41,78 @@ def handle(self, *args, **options): exit(-1) spreadsheet_rows = load_spreadsheet() - for index, (file_fix, date_string, title, url, _) in enumerate(spreadsheet_rows): - name = url.split("/")[6].lower() - if name.startswith("iabs"): - name = name[5:] - elif name.startswith("iab"): - name = name[4:] - if index == 1: - name += "-archive" # https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/ - if index == 100: - name = "2010-" + name # https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/ - if index == 152: - name = "2018-" + name # https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/ - docname = f"statement-iab-{xslugify(name)}" - ext = None - base_sourcename = f"{date_string}-{file_fix}" if file_fix != "" else date_string - if Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.md").exists(): - ext = "md" - elif Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.pdf").exists(): - ext="pdf" - if ext is None: - debug.show('f"Could not find {Path(tmpdir).joinpath("iab_statements", f"{base_path}.md")}"') - continue - filename = f"{docname}-00.{ext}" - # Create Document - doc = Document.objects.create( - name=docname, - type_id="statement", - title=title, - group_id=7, # The IAB group - rev="00", - uploaded_filename=filename, - ) - DocAlias.objects.create(name=doc.name).docs.add(doc) - year, month, day = [int(part) for part in date_string.split("-")] - e1 = DocEvent.objects.create( - time = datetime.datetime(year, month, day, 12, 00, tzinfo=datetime.timezone.utc), - type="published_statement", - doc=doc, - rev="00", - by_id=1, - desc="Statement published (note: The 1200Z time of day is inaccurate - the actual time of day is not known)" - ) - e2 = DocEvent.objects.create( - type="added_comment", - doc=doc, - rev="00", - by_id=1, # The "(System)" person - desc="Statement moved into datatracker from iab wordpress website", - ) - doc.save_with_history([e1, e2]) + with open('iab_statement_redirects.csv','w') as redirect_file: + redirect_writer = csv.writer(redirect_file) + for index, (file_fix, date_string, title, url, _) in enumerate(spreadsheet_rows): + name = url.split("/")[6].lower() + if name.startswith("iabs"): + name = name[5:] + elif name.startswith("iab"): + name = name[4:] + if index == 1: + name += "-archive" # https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/ + if index == 100: + name = "2010-" + name # https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/ + if index == 152: + name = "2018-" + name # https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/ + docname = f"statement-iab-{xslugify(name)}" + ext = None + base_sourcename = f"{date_string}-{file_fix}" if file_fix != "" else date_string + if Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.md").exists(): + ext = "md" + elif Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.pdf").exists(): + ext="pdf" + if ext is None: + debug.show('f"Could not find {Path(tmpdir).joinpath("iab_statements", f"{base_path}.md")}"') + continue + filename = f"{docname}-00.{ext}" + # Create Document + doc = Document.objects.create( + name=docname, + type_id="statement", + title=title, + group_id=7, # The IAB group + rev="00", + uploaded_filename=filename, + ) + DocAlias.objects.create(name=doc.name).docs.add(doc) + year, month, day = [int(part) for part in date_string.split("-")] + e1 = DocEvent.objects.create( + time = datetime.datetime(year, month, day, 12, 00, tzinfo=datetime.timezone.utc), + type="published_statement", + doc=doc, + rev="00", + by_id=1, + desc="Statement published (note: The 1200Z time of day is inaccurate - the actual time of day is not known)" + ) + e2 = DocEvent.objects.create( + type="added_comment", + doc=doc, + rev="00", + by_id=1, # The "(System)" person + desc="Statement moved into datatracker from iab wordpress website", + ) + doc.save_with_history([e1, e2]) - # Put file in place - source = Path(tmpdir).joinpath( - "iab_statements", f"{base_sourcename}.{ext}" - ) - dest = Path(settings.DOCUMENT_PATH_PATTERN.format(doc=doc)).joinpath( - filename - ) - if dest.exists(): - print(f"WARNING: {dest} already exists - not overwriting it.") - else: - os.makedirs(dest.parent, exist_ok=True) - shutil.copy(source, dest) + # Put file in place + source = Path(tmpdir).joinpath( + "iab_statements", f"{base_sourcename}.{ext}" + ) + dest = Path(settings.DOCUMENT_PATH_PATTERN.format(doc=doc)).joinpath( + filename + ) + if dest.exists(): + print(f"WARNING: {dest} already exists - not overwriting it.") + else: + os.makedirs(dest.parent, exist_ok=True) + shutil.copy(source, dest) + + redirect_writer.writerow( + [ + url, + f"https://datatracker.ietf.org/doc/{docname}", + ] + ) shutil.rmtree(tmpdir) From dac0cd39acf219d2a6142f2b445a1556a3611046 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 27 Jun 2023 13:40:37 -0500 Subject: [PATCH 11/19] fix: set document state on import --- ietf/doc/management/commands/import_iab_statements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/doc/management/commands/import_iab_statements.py b/ietf/doc/management/commands/import_iab_statements.py index 5e169a9adce..d4fdd10adb5 100644 --- a/ietf/doc/management/commands/import_iab_statements.py +++ b/ietf/doc/management/commands/import_iab_statements.py @@ -16,7 +16,7 @@ from django.conf import settings from django.core.management.base import BaseCommand -from ietf.doc.models import Document, DocAlias, DocEvent +from ietf.doc.models import Document, DocAlias, DocEvent, State from ietf.utils.text import xslugify class Command(BaseCommand): @@ -75,6 +75,7 @@ def handle(self, *args, **options): rev="00", uploaded_filename=filename, ) + doc.set_state(State.objects.get(type_id="statement",slug="active")) DocAlias.objects.create(name=doc.name).docs.add(doc) year, month, day = [int(part) for part in date_string.split("-")] e1 = DocEvent.objects.create( From 5aae1e4704cd1212ca642ad94f08f2087ab224ef Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 27 Jun 2023 13:48:47 -0500 Subject: [PATCH 12/19] feat: show published date on main doc view --- ietf/doc/views_doc.py | 2 ++ ietf/templates/doc/document_statement.html | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 5fd22141fe3..ae3f96e109b 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -813,12 +813,14 @@ def document_main(request, name, rev=None, document_html=False): content = markdown.markdown(doc.text_or_error()) can_manage = has_role(request.user,["Secretariat"]) # Add IAB or IESG as appropriate interesting_relations_that, interesting_relations_that_doc = interesting_doc_relations(doc) + published = doc.latest_event(type="published_statement").time return render(request, "doc/document_statement.html", dict(doc=doc, top=top, revisions=revisions, latest_rev=latest_rev, + published=published, content=content, snapshot=snapshot, replaces=interesting_relations_that_doc.filter(relationship="replaces"), diff --git a/ietf/templates/doc/document_statement.html b/ietf/templates/doc/document_statement.html index 411b7bacb31..bec3a72d1d1 100644 --- a/ietf/templates/doc/document_statement.html +++ b/ietf/templates/doc/document_statement.html @@ -34,7 +34,13 @@ - Last updated + Published + + {{ published|date:"Y-m-d" }} + + + + Metadata last updated {{ doc.time|date:"Y-m-d" }} From 0386acf4490eff4a2d8f62f85ee3001774eeedd2 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 28 Jun 2023 12:07:56 -0500 Subject: [PATCH 13/19] feat: handle pdf statements --- ietf/doc/factories.py | 8 ++- ietf/doc/tests_statement.py | 67 ++++++++++++++++++++-- ietf/doc/urls.py | 1 + ietf/doc/urls_statement.py | 9 +++ ietf/doc/views_doc.py | 18 +++++- ietf/doc/views_statement.py | 19 ++++++ ietf/settings.py | 1 + ietf/templates/doc/document_statement.html | 2 +- 8 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 ietf/doc/urls_statement.py diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index 088dfc9ea9f..140b536f43f 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -23,7 +23,6 @@ from ietf.utils.timezone import date_today - def draft_name_generator(type_id,group,n): return '%s-%s-%s-%s%d'%( type_id, @@ -587,6 +586,13 @@ class StatementFactory(BaseDocumentFactory): name = factory.LazyAttribute(lambda o: 'statement-%s-%s'%(xslugify(o.group.acronym), xslugify(o.title))) uploaded_filename = factory.LazyAttribute(lambda o: f"{o.name}-{o.rev}.md") + published_statement_event = factory.RelatedFactory( + 'ietf.doc.factories.DocEventFactory', + 'doc', + type="published_statement", + time=timezone.now()-datetime.timedelta(days=1) + ) + @factory.post_generation def states(obj, create, extracted, **kwargs): if not create: diff --git a/ietf/doc/tests_statement.py b/ietf/doc/tests_statement.py index b8092271d8b..7440a1556e8 100644 --- a/ietf/doc/tests_statement.py +++ b/ietf/doc/tests_statement.py @@ -9,23 +9,29 @@ from django.conf import settings from django.urls import reverse as urlreverse -from ietf.doc.factories import StatementFactory +from ietf.doc.factories import StatementFactory, DocEventFactory from ietf.doc.models import State from ietf.utils.test_utils import TestCase class StatementsTestCase(TestCase): + settings_temp_path_overrides = TestCase.settings_temp_path_overrides + [ "DOCUMENT_PATH_PATTERN" ] - def write_statement_file(self, statement): + def extract_content(self, response): + if not hasattr(response,"_cached_extraction"): + response._cached_extraction = list(response.streaming_content)[0].decode("utf-8") + return response._cached_extraction + + def write_statement_markdown_file(self, statement): ( Path(settings.DOCUMENT_PATH_PATTERN.format(doc=statement)) / ("%s-%s.md" % (statement.name, statement.rev)) ).write_text( - """# This is a test bofreq. -Version: {bofreq.rev} + """# This is a test statement. +Version: {statement.rev} ## A section @@ -33,9 +39,17 @@ def write_statement_file(self, statement): """ ) + def write_statement_pdf_file(self, statement): + ( + Path(settings.DOCUMENT_PATH_PATTERN.format(doc=statement)) + / ("%s-%s.pdf" % (statement.name, statement.rev)) + ).write_text( + f"{statement.rev} This is not valid PDF, but the test does not need it to be" + ) + def test_statement_doc_view(self): doc = StatementFactory() - self.write_statement_file(doc) + self.write_statement_markdown_file(doc) url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)) response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -43,6 +57,8 @@ def test_statement_doc_view(self): self.assertEqual(q("#statement-state").text(), "Active") self.assertEqual(q("#statement-type").text(), "IAB Statement") self.assertIn("has some text", q(".card-body").text()) + published = doc.docevent_set.filter(type="published_statement").last().time + self.assertIn(published.date().isoformat(), q("#published").text()) doc.set_state(State.objects.get(type_id="statement", slug="replaced")) doc2 = StatementFactory() @@ -64,3 +80,44 @@ def test_statement_doc_view(self): q = PyQuery(response.content) self.assertEqual(q("#statement-type").text(), "IAB Statement") self.assertEqual(q("#statement-type").next().text(), f"Replaces {doc.name}") + + def test_serve_pdf(self): + url = urlreverse( + "ietf.doc.views_statement.serve_pdf", + kwargs=dict(name="statement-does-not-exist"), + ) + r = self.client.get(url) + self.assertEqual(r.status_code, 404) + + doc = StatementFactory() + url = urlreverse( + "ietf.doc.views_statement.serve_pdf", kwargs=dict(name=doc.name) + ) + r = self.client.get(url) + self.assertEqual(r.status_code, 404) # File not found + + self.write_statement_pdf_file(doc) + doc.rev = "01" + e = DocEventFactory(type="published_statement", doc=doc, rev=doc.rev) + doc.save_with_history([e]) + self.write_statement_pdf_file(doc) + + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertEqual(r.get("Content-Type"), "application/pdf") + self.assertTrue( + self.extract_content(r).startswith(doc.rev) + ) # relies on test doc not actually being pdf + + url = urlreverse( + "ietf.doc.views_statement.serve_pdf", kwargs=dict(name=doc.name, rev="00") + ) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(self.extract_content(r).startswith("00 ")) + url = urlreverse( + "ietf.doc.views_statement.serve_pdf", kwargs=dict(name=doc.name, rev="01") + ) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(self.extract_content(r).startswith("01 ")) diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index f4b67280450..d022adef0ea 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -169,6 +169,7 @@ url(r'^%(charter)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_charter')), url(r'^%(bofreq)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_bofreq')), + url(r'^%(statement)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_statement')), url(r'^%(name)s/conflict-review/' % settings.URL_REGEXPS, include('ietf.doc.urls_conflict_review')), url(r'^%(name)s/status-change/' % settings.URL_REGEXPS, include('ietf.doc.urls_status_change')), url(r'^%(name)s/material/' % settings.URL_REGEXPS, include('ietf.doc.urls_material')), diff --git a/ietf/doc/urls_statement.py b/ietf/doc/urls_statement.py new file mode 100644 index 00000000000..8d483c37e13 --- /dev/null +++ b/ietf/doc/urls_statement.py @@ -0,0 +1,9 @@ +# Copyright The IETF Trust 2023, All Rights Reserved + +from django.conf import settings +from ietf.doc import views_statement +from ietf.utils.urls import url + +urlpatterns = [ + url(r"^(?:%(rev)s/)?pdf/$" % settings.URL_REGEXPS, views_statement.serve_pdf), +] diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index ae3f96e109b..21a36651e20 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -810,7 +810,23 @@ def document_main(request, name, rev=None, document_html=False): ) if doc.type_id == "statement": - content = markdown.markdown(doc.text_or_error()) + if doc.uploaded_filename: + basename = doc.uploaded_filename.split(".")[0] # strip extension + else: + basename = doc.name + variants = set([match.name.split(".")[1] for match in Path(doc.get_file_path()).glob(f"{basename}.*")]) + inlineable = any([ext in variants for ext in ["md", "txt"]]) + if inlineable: + content = markdown.markdown(doc.text_or_error()) + else: + content = "No format available to display inline" + if "pdf" in variants: + pdf_url = urlreverse( + "ietf.doc.views_statement.serve_pdf", + kwargs=dict(name=doc.name, rev=doc.rev), + ) + content += f" - Download [pdf]({pdf_url})" + content = markdown.markdown(content) can_manage = has_role(request.user,["Secretariat"]) # Add IAB or IESG as appropriate interesting_relations_that, interesting_relations_that_doc = interesting_doc_relations(doc) published = doc.latest_event(type="published_statement").time diff --git a/ietf/doc/views_statement.py b/ietf/doc/views_statement.py index 00f62bebbd5..89d65ae223c 100644 --- a/ietf/doc/views_statement.py +++ b/ietf/doc/views_statement.py @@ -1 +1,20 @@ # Copyright The IETF Trust 2023, All Rights Reserved + +from pathlib import Path + +from django.http import FileResponse, Http404 +from django.views.decorators.cache import cache_control +from django.shortcuts import get_object_or_404 + +from ietf.doc.models import Document + +@cache_control(max_age=3600) +def serve_pdf(self, name, rev=None): + doc = get_object_or_404(Document, name=name) + if rev is None: + rev = doc.rev + p = Path(doc.get_file_path()).joinpath(f"{doc.name}-{rev}.pdf") + if not p.exists(): + raise Http404 + else: + return FileResponse(p.open(mode="rb"),content_type="application/pdf") diff --git a/ietf/settings.py b/ietf/settings.py index 4aceca65f56..762696888fb 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -657,6 +657,7 @@ def skip_unreadable_post(record): "acronym": r"(?P[-a-z0-9]+)", "bofreq": r"(?Pbofreq-[-a-z0-9]+)", "charter": r"(?Pcharter-[-a-z0-9]+)", + "statement": r"(?Pstatement-[-a-z0-9]+)", "date": r"(?P\d{4}-\d{2}-\d{2})", "name": r"(?P[A-Za-z0-9._+-]+?)", "document": r"(?P[a-z][-a-z0-9]+)", # regular document names diff --git a/ietf/templates/doc/document_statement.html b/ietf/templates/doc/document_statement.html index bec3a72d1d1..a6ef59ec55e 100644 --- a/ietf/templates/doc/document_statement.html +++ b/ietf/templates/doc/document_statement.html @@ -36,7 +36,7 @@ Published - {{ published|date:"Y-m-d" }} + {{ published|date:"Y-m-d" }} From 583c04b73b0a5b4e112e3b3b205fd7dacc8401af Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 29 Jun 2023 13:45:45 -0500 Subject: [PATCH 14/19] feat: create new and update statements --- ietf/doc/models.py | 2 +- ietf/doc/tests_statement.py | 244 +++++++++++++++- ietf/doc/urls.py | 3 +- ietf/doc/urls_statement.py | 1 + ietf/doc/views_doc.py | 2 +- ietf/doc/views_statement.py | 261 +++++++++++++++++- ietf/static/js/upload_statement.js | 29 ++ .../doc/statement/new_statement.html | 18 ++ .../doc/statement/statement_template.md | 1 + .../doc/statement/upload_content.html | 23 ++ package.json | 1 + 11 files changed, 574 insertions(+), 11 deletions(-) create mode 100644 ietf/static/js/upload_statement.js create mode 100644 ietf/templates/doc/statement/new_statement.html create mode 100644 ietf/templates/doc/statement/statement_template.md create mode 100644 ietf/templates/doc/statement/upload_content.html diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 6c8fae45a24..fc82b184576 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -186,7 +186,7 @@ def get_base_name(self): 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 - elif self.type_id == 'bofreq': + elif self.type_id in ['bofreq', 'statement']: self._cached_base_name = "%s-%s.md" % (self.name, self.rev) else: if self.rev: diff --git a/ietf/doc/tests_statement.py b/ietf/doc/tests_statement.py index 7440a1556e8..965936e9b81 100644 --- a/ietf/doc/tests_statement.py +++ b/ietf/doc/tests_statement.py @@ -2,27 +2,39 @@ import debug # pyflakes:ignore +import os + from pyquery import PyQuery from pathlib import Path from django.conf import settings +from django.core.files.uploadedfile import SimpleUploadedFile +from django.template.loader import render_to_string from django.urls import reverse as urlreverse from ietf.doc.factories import StatementFactory, DocEventFactory -from ietf.doc.models import State -from ietf.utils.test_utils import TestCase +from ietf.doc.models import Document, DocAlias, State, NewRevisionDocEvent +from ietf.group.models import Group +from ietf.person.factories import PersonFactory +from ietf.utils.mail import outbox, empty_outbox +from ietf.utils.test_utils import ( + TestCase, + reload_db_objects, + login_testing_unauthorized, +) class StatementsTestCase(TestCase): - settings_temp_path_overrides = TestCase.settings_temp_path_overrides + [ "DOCUMENT_PATH_PATTERN" ] def extract_content(self, response): - if not hasattr(response,"_cached_extraction"): - response._cached_extraction = list(response.streaming_content)[0].decode("utf-8") + if not hasattr(response, "_cached_extraction"): + response._cached_extraction = list(response.streaming_content)[0].decode( + "utf-8" + ) return response._cached_extraction def write_statement_markdown_file(self, statement): @@ -121,3 +133,225 @@ def test_serve_pdf(self): r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertTrue(self.extract_content(r).startswith("01 ")) + + def test_submit(self): + doc = StatementFactory() + url = urlreverse("ietf.doc.views_statement.submit", kwargs=dict(name=doc.name)) + + rev = doc.rev + r = self.client.post( + url, {"statement_submission": "enter", "statement_content": "# oiwefrase"} + ) + self.assertEqual(r.status_code, 302) + doc = reload_db_objects(doc) + self.assertEqual(rev, doc.rev) + + nobody = PersonFactory() + self.client.login( + username=nobody.user.username, password=nobody.user.username + "+password" + ) + r = self.client.post( + url, {"statement_submission": "enter", "statement_content": "# oiwefrase"} + ) + self.assertEqual(r.status_code, 403) + doc = reload_db_objects(doc) + self.assertEqual(rev, doc.rev) + self.client.logout() + + for username in ["secretary"]: # There is potential for expanding this list + self.client.login(username=username, password=username + "+password") + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + file = SimpleUploadedFile( + "random.pdf", + b"not valid pdf", + content_type="application/pdf", + ) + for postdict in [ + { + "statement_submission": "enter", + "statement_content": f"# {username}", + }, + { + "statement_submission": "upload", + "statement_file": file, + }, + ]: + docevent_count = doc.docevent_set.count() + empty_outbox() + r = self.client.post(url, postdict) + self.assertEqual(r.status_code, 302) + doc = reload_db_objects(doc) + self.assertEqual("%02d" % (int(rev) + 1), doc.rev) + if postdict["statement_submission"] == "enter": + self.assertEqual(f"# {username}", doc.text()) + else: + self.assertEqual(f"not valid pdf", doc.text()) + self.assertEqual(docevent_count + 1, doc.docevent_set.count()) + self.assertEqual(0, len(outbox)) + rev = doc.rev + self.client.logout() + + def test_start_new_statement(self): + url = urlreverse("ietf.doc.views_statement.new_statement") + login_testing_unauthorized(self, "secretary", url) + r = self.client.get(url) + self.assertContains( + r, + "Replace this with the content of the statement in markdown source", + status_code=200, + ) + group = Group.objects.get(acronym="iab") + r = self.client.post( + url, + dict( + group=group.pk, + title="default", + statement_submission="enter", + statement_content=render_to_string( + "doc/statement/statement_template.md", {"settings": settings} + ), + ), + ) + self.assertContains(r, "The example content may not be saved.", status_code=200) + + file = SimpleUploadedFile( + "random.pdf", + b"not valid pdf", + content_type="application/pdf", + ) + group = Group.objects.get(acronym="iab") + for postdict in [ + dict( + group=group.pk, + title="title one", + statement_submission="enter", + statement_content="some stuff", + ), + dict( + group=group.pk, + title="title two", + statement_submission="upload", + statement_file=file, + ), + ]: + empty_outbox() + r = self.client.post(url, postdict) + self.assertEqual(r.status_code, 302) + name = f"statement-{group.acronym}-{postdict['title']}".replace( + " ", "-" + ) # cheap slugification + statement = Document.objects.filter( + name=name, type_id="statement" + ).first() + self.assertIsNotNone(statement) + self.assertIsNotNone(DocAlias.objects.filter(name=name).first()) + self.assertEqual(statement.title, postdict["title"]) + self.assertEqual(statement.rev, "00") + self.assertEqual(statement.get_state_slug(), "active") + self.assertEqual( + statement.latest_event(NewRevisionDocEvent).rev, "00" + ) + self.assertIsNotNone(statement.latest_event(type="published_statement")) + if postdict["statement_submission"] == "enter": + self.assertEqual(statement.text_or_error(), "some stuff") + else: + self.assertTrue(statement.uploaded_filename.endswith("pdf")) + self.assertEqual(len(outbox), 0) + + existing_statement = StatementFactory() + for postdict in [ + dict( + group=group.pk, + title="", + statement_submission="enter", + statement_content="some stuff", + ), + dict( + group=group.pk, + title="a title", + statement_submission="enter", + statement_content="", + ), + dict( + group=group.pk, + title=existing_statement.title, + statement_submission="enter", + statement_content="some stuff", + ), + dict( + group=group.pk, + title="森川", + statement_submission="enter", + statement_content="some stuff", + ), + dict( + group=group.pk, + title="a title", + statement_submission="", + statement_content="some stuff", + ), + dict( + group="", + title="a title", + statement_submission="enter", + statement_content="some stuff", + ), + dict( + group=0, + title="a title", + statement_submission="enter", + statement_content="some stuff", + ), + ]: + r = self.client.post(url, postdict) + self.assertEqual(r.status_code, 200, f"Wrong status_code for {postdict}") + q = PyQuery(r.content) + self.assertTrue( + q("form div.is-invalid"), f"Expected an error for {postdict}" + ) + + def test_submit_non_markdown_formats(self): + doc = StatementFactory() + + file = SimpleUploadedFile( + "random.pdf", + b"01 This is not valid PDF, but the test does not need it to be", + content_type="application/pdf", + ) + + url = urlreverse("ietf.doc.views_statement.submit", kwargs=dict(name=doc.name)) + login_testing_unauthorized(self, "secretary", url) + + r = self.client.post( + url, + { + "statement_submission": "upload", + "statement_file": file, + }, + ) + self.assertEqual(r.status_code, 302) + self.assertEqual( + r["Location"], + urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)), + ) + + doc = reload_db_objects(doc) + self.assertEqual(doc.rev, "01") + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual( + q("#id_statement_content").text().strip(), + "The current revision of this statement is in pdf format", + ) + + file = SimpleUploadedFile( + "random.mp4", b"29ucdvn2o09hano5", content_type="video/mp4" + ) + r = self.client.post( + url, {"statement_submission": "upload", "statement_file": file} + ) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue("Unexpected content" in q("#id_statement_file").next().text()) diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index d022adef0ea..a1b4f0bd938 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -37,7 +37,7 @@ from django.urls import include from django.views.generic import RedirectView -from ietf.doc import views_search, views_draft, views_ballot, views_status_change, views_doc, views_downref, views_stats, views_help, views_bofreq +from ietf.doc import views_search, views_draft, views_ballot, views_status_change, views_doc, views_downref, views_stats, views_help, views_bofreq, views_statement from ietf.utils.urls import url session_patterns = [ @@ -57,6 +57,7 @@ url(r'^start-rfc-status-change/(?:%(name)s/)?$' % settings.URL_REGEXPS, views_status_change.start_rfc_status_change), url(r'^bof-requests/?$', views_bofreq.bof_requests), url(r'^bof-requests/new/$', views_bofreq.new_bof_request), + url(r'^statement/new/$', views_statement.new_statement), url(r'^iesg/?$', views_search.drafts_in_iesg_process), url(r'^email-aliases/?$', views_doc.email_aliases), url(r'^downref/?$', views_downref.downref_registry), diff --git a/ietf/doc/urls_statement.py b/ietf/doc/urls_statement.py index 8d483c37e13..50dc089bb09 100644 --- a/ietf/doc/urls_statement.py +++ b/ietf/doc/urls_statement.py @@ -6,4 +6,5 @@ urlpatterns = [ url(r"^(?:%(rev)s/)?pdf/$" % settings.URL_REGEXPS, views_statement.serve_pdf), + url(r'^submit/$', views_statement.submit), ] diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 21a36651e20..01531b71327 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -813,7 +813,7 @@ def document_main(request, name, rev=None, document_html=False): if doc.uploaded_filename: basename = doc.uploaded_filename.split(".")[0] # strip extension else: - basename = doc.name + basename = f"{doc.name}-{doc.rev}" variants = set([match.name.split(".")[1] for match in Path(doc.get_file_path()).glob(f"{basename}.*")]) inlineable = any([ext in variants for ext in ["md", "txt"]]) if inlineable: diff --git a/ietf/doc/views_statement.py b/ietf/doc/views_statement.py index 89d65ae223c..a07ed39fd22 100644 --- a/ietf/doc/views_statement.py +++ b/ietf/doc/views_statement.py @@ -1,12 +1,24 @@ # Copyright The IETF Trust 2023, All Rights Reserved +import debug # pyflakes: ignore + from pathlib import Path +from django import forms +from django.conf import settings from django.http import FileResponse, Http404 from django.views.decorators.cache import cache_control -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, render, redirect +from django.template.loader import render_to_string +from ietf.utils import markdown +from django.utils.html import escape + +from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, State +from ietf.group.models import Group +from ietf.ietfauth.utils import role_required +from ietf.utils.text import xslugify +from ietf.utils.textupload import get_cleaned_text_file_content -from ietf.doc.models import Document @cache_control(max_age=3600) def serve_pdf(self, name, rev=None): @@ -17,4 +29,247 @@ def serve_pdf(self, name, rev=None): if not p.exists(): raise Http404 else: - return FileResponse(p.open(mode="rb"),content_type="application/pdf") + return FileResponse(p.open(mode="rb"), content_type="application/pdf") + + +class StatementUploadForm(forms.Form): + ACTIONS = [ + ("enter", "Enter content directly"), + ("upload", "Upload content from file"), + ] + statement_submission = forms.ChoiceField(choices=ACTIONS, widget=forms.RadioSelect) + statement_file = forms.FileField( + label="Markdown or PDF source file to upload", required=False + ) + statement_content = forms.CharField( + widget=forms.Textarea(attrs={"rows": 30}), required=False, strip=False + ) + + def clean(self): + def require_field(f): + if not self.cleaned_data.get(f): + self.add_error(f, forms.ValidationError("You must fill in this field.")) + return False + else: + return True + + submission_method = self.cleaned_data.get("statement_submission") + markdown_content = "" + if submission_method == "enter": + if require_field("statement_content"): + markdown_content = self.cleaned_data["statement_content"].replace( + "\r", "" + ) + default_content = render_to_string( + "doc/statement/statement_template.md", {"settings": settings} + ) + if markdown_content == default_content: + raise forms.ValidationError( + "The example content may not be saved. Edit it to contain the next revision statement content." + ) + if ( + markdown_content + == "The current revision of this statement is in pdf format" + ): + raise forms.ValidationError( + "Not proceeding with the text noting that the current version is pdf. Did you mean to upload a new PDF?" + ) + elif submission_method == "upload": + if require_field("statement_file"): + content_type = self.cleaned_data["statement_file"].content_type + acceptable_types = ( + "application/pdf", + ) + settings.DOC_TEXT_FILE_VALID_UPLOAD_MIME_TYPES + if not content_type.startswith( + acceptable_types + ): # dances around decoration of types with encoding etc. + self.add_error( + "statement_file", + forms.ValidationError( + f"Unexpected content type: Expected one of {', '.join(acceptable_types)}" + ), + ) + elif content_type != "application/pdf": + markdown_content = get_cleaned_text_file_content( + self.cleaned_data["statement_file"] + ) + if markdown_content != "": + try: + _ = markdown.markdown(markdown_content) + except Exception as e: + raise forms.ValidationError(f"Markdown processing failed: {e}") + + +@role_required("Secretariat") +def submit(request, name): + statement = get_object_or_404(Document, type="statement", name=name) + + if request.method == "POST": + form = StatementUploadForm(request.POST, request.FILES) + if form.is_valid(): + statement_submission = form.cleaned_data["statement_submission"] + writing_pdf = ( + statement_submission == "upload" + and form.cleaned_data["statement_file"].content_type + == "application/pdf" + ) + + statement.rev = "%02d" % (int(statement.rev) + 1) + statement.uploaded_filename = ( + f"{statement.name}-{statement.rev}.{'pdf' if writing_pdf else 'md'}" + ) + e = NewRevisionDocEvent.objects.create( + type="new_revision", + doc=statement, + by=request.user.person, + rev=statement.rev, + desc="New revision available", + ) + statement.save_with_history([e]) + markdown_content = "" + if statement_submission == "upload": + if not writing_pdf: + markdown_content = get_cleaned_text_file_content( + form.cleaned_data["statement_file"] + ) + else: + markdown_content = form.cleaned_data["statement_content"] + with Path(statement.get_file_name()).open( + mode="wb" if writing_pdf else "w" + ) as destination: + if writing_pdf: + for chunk in form.cleaned_data["statement_file"].chunks(): + destination.write(chunk) + else: + destination.write(markdown_content) + return redirect("ietf.doc.views_doc.document_main", name=statement.name) + + else: + if statement.uploaded_filename.endswith("pdf"): + text = "The current revision of this statement is in pdf format" + else: + text = statement.text_or_error() + init = { + "statement_content": text, + "statement_submission": "enter", + } + form = StatementUploadForm(initial=init) + return render( + request, "doc/statement/upload_content.html", {"form": form, "doc": statement} + ) + + +class NewStatementForm(StatementUploadForm): + group = forms.ModelChoiceField( + queryset=Group.objects.filter(acronym__in=["iab", "iesg"]) + ) + title = forms.CharField(max_length=255) + field_order = [ + "group", + "title", + "statement_submission", + "statement_file", + "statement_content", + ] + + def name_from_title_and_group(self, title, group): + title_slug = xslugify(title) + if title_slug.startswith(f"{group.acronym}-"): + title_slug = title_slug[len(f"{group.acronym}-") :] + name = f"statement-{group.acronym}-{title_slug[:240]}" + return name.replace("_", "-") + + def clean(self): + if all([field in self.cleaned_data for field in ["title", "group"]]): + title = self.cleaned_data["title"] + group = self.cleaned_data["group"] + name = self.name_from_title_and_group(title, group) + if name == self.name_from_title_and_group("", group): + self.add_error( + "title", + forms.ValidationError( + "The filename derived from this title is empty. Please include a few descriptive words using ascii or numeric characters" + ), + ) + if Document.objects.filter(name=name).exists(): + self.add_error( + "title", + forms.ValidationError( + "This title produces a filename already used by an existing statement" + ), + ) + return super().clean() + + +@role_required("Secretariat") +def new_statement(request): + if request.method == "POST": + form = NewStatementForm(request.POST, request.FILES) + if form.is_valid(): + statement_submission = form.cleaned_data["statement_submission"] + writing_pdf = ( + statement_submission == "upload" + and form.cleaned_data["statement_file"].content_type + == "application/pdf" + ) + + group = form.cleaned_data["group"] + title = form.cleaned_data["title"] + name = form.name_from_title_and_group(title, group) + statement = Document.objects.create( + type_id="statement", + group=group, + name=name, + title=title, + abstract="", + rev="00", + uploaded_filename=f"{name}-00.{'pdf' if writing_pdf else 'md'}", + ) + statement.set_state(State.objects.get(type_id="statement", slug="active")) + e1 = NewRevisionDocEvent.objects.create( + type="new_revision", + doc=statement, + by=request.user.person, + rev=statement.rev, + desc="New revision available", + time=statement.time, + ) + e2 = DocEvent.objects.create( + type="published_statement", + doc=statement, + rev=statement.rev, + by=request.user.person, + desc="Statement published", + ) + statement.save_with_history([e1, e2]) + alias = DocAlias.objects.create(name=name) + alias.docs.set([statement]) + markdown_content = "" + if statement_submission == "upload": + if not writing_pdf: + markdown_content = get_cleaned_text_file_content( + form.cleaned_data["statement_file"] + ) + else: + markdown_content = form.cleaned_data["statement_content"] + with Path(statement.get_file_name()).open( + mode="wb" if writing_pdf else "w" + ) as destination: + if writing_pdf: + for chunk in form.cleaned_data["statement_file"].chunks(): + destination.write(chunk) + else: + destination.write(markdown_content) + return redirect("ietf.doc.views_doc.document_main", name=statement.name) + + else: + init = { + "statement_content": escape( + render_to_string( + "doc/statement/statement_template.md", {"settings": settings} + ) + ), + "statement_submission": "enter", + } + form = NewStatementForm(initial=init) + return render(request, "doc/statement/new_statement.html", {"form": form}) diff --git a/ietf/static/js/upload_statement.js b/ietf/static/js/upload_statement.js new file mode 100644 index 00000000000..7e8a4e8643a --- /dev/null +++ b/ietf/static/js/upload_statement.js @@ -0,0 +1,29 @@ +$(document) + .ready(function () { + var form = $("form.upload-content"); + // review submission selection + form.find("[name=statement_submission]") + .on("click change", function () { + var val = form.find("[name=statement_submission]:checked") + .val(); + + var shouldBeVisible = { + enter: ['[name="statement_content"]'], + upload: ['[name="statement_file"]'], + }; + + for (var v in shouldBeVisible) { + for (var i in shouldBeVisible[v]) { + var selector = shouldBeVisible[v][i]; + var row = form.find(selector); + if (!row.is(".row")) + row = row.closest(".row"); + if ($.inArray(selector, shouldBeVisible[val]) != -1) + row.show(); + else + row.hide(); + } + } + }) + .trigger("change"); + }); diff --git a/ietf/templates/doc/statement/new_statement.html b/ietf/templates/doc/statement/new_statement.html new file mode 100644 index 00000000000..0742314ed19 --- /dev/null +++ b/ietf/templates/doc/statement/new_statement.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2023, All Rights Reserved #} +{% load origin django_bootstrap5 static textfilters %} +{% block title %}Start a new Statement{% endblock %} +{% block content %} + {% origin %} +

Start a new Statement

+
+ {% csrf_token %} + {% bootstrap_form form layout="horizontal" %} + +
+{% endblock %} +{% block js %} + +{% endblock %} \ No newline at end of file diff --git a/ietf/templates/doc/statement/statement_template.md b/ietf/templates/doc/statement/statement_template.md new file mode 100644 index 00000000000..cc311530ec5 --- /dev/null +++ b/ietf/templates/doc/statement/statement_template.md @@ -0,0 +1 @@ +Replace this with the content of the statement in markdown source diff --git a/ietf/templates/doc/statement/upload_content.html b/ietf/templates/doc/statement/upload_content.html new file mode 100644 index 00000000000..712c44043dc --- /dev/null +++ b/ietf/templates/doc/statement/upload_content.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2023, All Rights Reserved #} +{% load origin django_bootstrap5 static %} +{% block title %}Upload new revision: {{ doc.name }}{% endblock %} +{% block content %} + {% origin %} +

+ Upload New Revision +
+ {{ doc.name }} +

+
+ {% csrf_token %} + {% bootstrap_form form layout="horizontal" %} + + Back +
+{% endblock %} +{% block js %} + +{% endblock %} \ No newline at end of file diff --git a/package.json b/package.json index c14c5017364..3470106d559 100644 --- a/package.json +++ b/package.json @@ -148,6 +148,7 @@ "ietf/static/js/upcoming.js", "ietf/static/js/upload-material.js", "ietf/static/js/upload_bofreq.js", + "ietf/static/js/upload_statement.js", "ietf/static/js/zxcvbn.js" ] }, From 1521f8e2bca2b339b9cdb44b048d38830ac67888 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 29 Jun 2023 13:56:41 -0500 Subject: [PATCH 15/19] chore: copyright block updates --- ietf/doc/factories.py | 2 +- ietf/doc/migrations/0005_alter_docevent_type.py | 2 +- ietf/doc/models.py | 2 +- ietf/doc/urls.py | 2 +- ietf/doc/views_doc.py | 2 +- ietf/doc/views_help.py | 2 ++ ietf/group/urls.py | 2 +- ietf/group/utils.py | 2 +- ietf/mailtrigger/utils.py | 2 +- ietf/templates/group/statements.html | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index 140b536f43f..e59ecfd9851 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2016-2020, All Rights Reserved +# Copyright The IETF Trust 2016-2023, All Rights Reserved # -*- coding: utf-8 -*- diff --git a/ietf/doc/migrations/0005_alter_docevent_type.py b/ietf/doc/migrations/0005_alter_docevent_type.py index dbc7d09e976..f8a3cfc7959 100644 --- a/ietf/doc/migrations/0005_alter_docevent_type.py +++ b/ietf/doc/migrations/0005_alter_docevent_type.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.2 on 2023-06-27 14:10 +# Copyright The IETF Trust 2023, All Rights Reserved from django.db import migrations, models diff --git a/ietf/doc/models.py b/ietf/doc/models.py index fc82b184576..d4803267633 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2010-2020, All Rights Reserved +# Copyright The IETF Trust 2010-2023, All Rights Reserved # -*- coding: utf-8 -*- diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index a1b4f0bd938..c2addc5c923 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2009-2020, All Rights Reserved +# Copyright The IETF Trust 2009-2023, All Rights Reserved # -*- coding: utf-8 -*- # Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). # All rights reserved. Contact: Pasi Eronen diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 01531b71327..de29ab3d8d0 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2009-2022, All Rights Reserved +# Copyright The IETF Trust 2009-2023, All Rights Reserved # -*- coding: utf-8 -*- # # Parts Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). diff --git a/ietf/doc/views_help.py b/ietf/doc/views_help.py index d474a33192a..fd46bda3102 100644 --- a/ietf/doc/views_help.py +++ b/ietf/doc/views_help.py @@ -1,3 +1,5 @@ +# Copyright The IETF Trust 2013-2023, All Rights Reserved + from django.shortcuts import render, get_object_or_404 from django.http import Http404 diff --git a/ietf/group/urls.py b/ietf/group/urls.py index da9b5af7845..e3fa1896cef 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2013-2020, All Rights Reserved +# Copyright The IETF Trust 2013-2023, All Rights Reserved from django.conf import settings from django.urls import include diff --git a/ietf/group/utils.py b/ietf/group/utils.py index 0ad13c6b170..b82474b497a 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2012-2021, All Rights Reserved +# Copyright The IETF Trust 2012-2023, All Rights Reserved # -*- coding: utf-8 -*- diff --git a/ietf/mailtrigger/utils.py b/ietf/mailtrigger/utils.py index 996a440fd5b..496f20dc285 100644 --- a/ietf/mailtrigger/utils.py +++ b/ietf/mailtrigger/utils.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2015-2019, All Rights Reserved +# Copyright The IETF Trust 2015-2023, All Rights Reserved from collections import namedtuple diff --git a/ietf/templates/group/statements.html b/ietf/templates/group/statements.html index 5e57fdee803..574ed81e748 100644 --- a/ietf/templates/group/statements.html +++ b/ietf/templates/group/statements.html @@ -1,5 +1,5 @@ {% extends "group/group_base.html" %} -{# Copyright The IETF Trust 2015, All Rights Reserved #} +{# Copyright The IETF Trust 2023, All Rights Reserved #} {% load origin %} {% load ietf_filters person_filters textfilters %} {% load static %} From ec210bcec05718ddd1715617814ac935341a3fef Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 29 Jun 2023 15:55:51 -0500 Subject: [PATCH 16/19] chore: remove flakes --- ietf/doc/tests_statement.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ietf/doc/tests_statement.py b/ietf/doc/tests_statement.py index 965936e9b81..48ab0da7d82 100644 --- a/ietf/doc/tests_statement.py +++ b/ietf/doc/tests_statement.py @@ -2,8 +2,6 @@ import debug # pyflakes:ignore -import os - from pyquery import PyQuery from pathlib import Path @@ -186,7 +184,7 @@ def test_submit(self): if postdict["statement_submission"] == "enter": self.assertEqual(f"# {username}", doc.text()) else: - self.assertEqual(f"not valid pdf", doc.text()) + self.assertEqual("not valid pdf", doc.text()) self.assertEqual(docevent_count + 1, doc.docevent_set.count()) self.assertEqual(0, len(outbox)) rev = doc.rev From 052e5759eb0beac5096a9d11fb8ebaaf882b44c7 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 29 Jun 2023 16:04:30 -0500 Subject: [PATCH 17/19] chore: black --- ietf/doc/factories.py | 22 ++++--- .../commands/import_iab_statements.py | 63 ++++++++++++------- ietf/doc/urls_statement.py | 2 +- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index e59ecfd9851..a8c20b6bb90 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -579,18 +579,20 @@ def reset_canonical_name(obj, create, extracted, **kwargs): return None class StatementFactory(BaseDocumentFactory): - type_id = 'statement' - title = factory.Faker('sentence') - group = factory.SubFactory('ietf.group.factories.GroupFactory', acronym='iab') + type_id = "statement" + title = factory.Faker("sentence") + group = factory.SubFactory("ietf.group.factories.GroupFactory", acronym="iab") - name = factory.LazyAttribute(lambda o: 'statement-%s-%s'%(xslugify(o.group.acronym), xslugify(o.title))) + name = factory.LazyAttribute( + lambda o: "statement-%s-%s" % (xslugify(o.group.acronym), xslugify(o.title)) + ) uploaded_filename = factory.LazyAttribute(lambda o: f"{o.name}-{o.rev}.md") published_statement_event = factory.RelatedFactory( - 'ietf.doc.factories.DocEventFactory', - 'doc', + "ietf.doc.factories.DocEventFactory", + "doc", type="published_statement", - time=timezone.now()-datetime.timedelta(days=1) + time=timezone.now() - datetime.timedelta(days=1), ) @factory.post_generation @@ -598,7 +600,7 @@ def states(obj, create, extracted, **kwargs): if not create: return if extracted: - for (state_type_id,state_slug) in extracted: - obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug)) + for state_type_id, state_slug in extracted: + obj.set_state(State.objects.get(type_id=state_type_id, slug=state_slug)) else: - obj.set_state(State.objects.get(type_id='statement',slug='active')) + obj.set_state(State.objects.get(type_id="statement", slug="active")) diff --git a/ietf/doc/management/commands/import_iab_statements.py b/ietf/doc/management/commands/import_iab_statements.py index d4fdd10adb5..c268101fa7a 100644 --- a/ietf/doc/management/commands/import_iab_statements.py +++ b/ietf/doc/management/commands/import_iab_statements.py @@ -1,6 +1,6 @@ # Copyright The IETF Trust 2023, All Rights Reserved -import debug # pyflakes:ignore +import debug # pyflakes:ignore import csv import datetime @@ -19,6 +19,7 @@ from ietf.doc.models import Document, DocAlias, DocEvent, State from ietf.utils.text import xslugify + class Command(BaseCommand): help = "Performs a one-time import of IAB statements" @@ -41,29 +42,47 @@ def handle(self, *args, **options): exit(-1) spreadsheet_rows = load_spreadsheet() - with open('iab_statement_redirects.csv','w') as redirect_file: + with open("iab_statement_redirects.csv", "w") as redirect_file: redirect_writer = csv.writer(redirect_file) - for index, (file_fix, date_string, title, url, _) in enumerate(spreadsheet_rows): + for index, (file_fix, date_string, title, url, _) in enumerate( + spreadsheet_rows + ): name = url.split("/")[6].lower() if name.startswith("iabs"): name = name[5:] elif name.startswith("iab"): name = name[4:] if index == 1: - name += "-archive" # https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/ + name += "-archive" # https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/ if index == 100: - name = "2010-" + name # https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/ + name = ( + "2010-" + name + ) # https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/ if index == 152: - name = "2018-" + name # https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/ + name = ( + "2018-" + name + ) # https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/ docname = f"statement-iab-{xslugify(name)}" ext = None - base_sourcename = f"{date_string}-{file_fix}" if file_fix != "" else date_string - if Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.md").exists(): + base_sourcename = ( + f"{date_string}-{file_fix}" if file_fix != "" else date_string + ) + if ( + Path(tmpdir) + .joinpath("iab_statements", f"{base_sourcename}.md") + .exists() + ): ext = "md" - elif Path(tmpdir).joinpath("iab_statements", f"{base_sourcename}.pdf").exists(): - ext="pdf" + elif ( + Path(tmpdir) + .joinpath("iab_statements", f"{base_sourcename}.pdf") + .exists() + ): + ext = "pdf" if ext is None: - debug.show('f"Could not find {Path(tmpdir).joinpath("iab_statements", f"{base_path}.md")}"') + debug.show( + 'f"Could not find {Path(tmpdir).joinpath("iab_statements", f"{base_path}.md")}"' + ) continue filename = f"{docname}-00.{ext}" # Create Document @@ -75,16 +94,18 @@ def handle(self, *args, **options): rev="00", uploaded_filename=filename, ) - doc.set_state(State.objects.get(type_id="statement",slug="active")) + doc.set_state(State.objects.get(type_id="statement", slug="active")) DocAlias.objects.create(name=doc.name).docs.add(doc) year, month, day = [int(part) for part in date_string.split("-")] e1 = DocEvent.objects.create( - time = datetime.datetime(year, month, day, 12, 00, tzinfo=datetime.timezone.utc), + time=datetime.datetime( + year, month, day, 12, 00, tzinfo=datetime.timezone.utc + ), type="published_statement", doc=doc, rev="00", by_id=1, - desc="Statement published (note: The 1200Z time of day is inaccurate - the actual time of day is not known)" + desc="Statement published (note: The 1200Z time of day is inaccurate - the actual time of day is not known)", ) e2 = DocEvent.objects.create( type="added_comment", @@ -93,7 +114,7 @@ def handle(self, *args, **options): by_id=1, # The "(System)" person desc="Statement moved into datatracker from iab wordpress website", ) - doc.save_with_history([e1, e2]) + doc.save_with_history([e1, e2]) # Put file in place source = Path(tmpdir).joinpath( @@ -107,7 +128,7 @@ def handle(self, *args, **options): else: os.makedirs(dest.parent, exist_ok=True) shutil.copy(source, dest) - + redirect_writer.writerow( [ url, @@ -115,13 +136,11 @@ def handle(self, *args, **options): ] ) - shutil.rmtree(tmpdir) + def load_spreadsheet(): - - csv_dump= \ -'''2002-03-01,IAB RFC Publication Process Description(txt) March 2003,https://www.iab.org/documents/correspondence-reports-documents/docs2003/iab-rfc-publication-process/,deprecated + csv_dump = '''2002-03-01,IAB RFC Publication Process Description(txt) March 2003,https://www.iab.org/documents/correspondence-reports-documents/docs2003/iab-rfc-publication-process/,deprecated 2015-01-27,IAB Statement on Identifiers and Unicode 7.0.0 (archive),https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/,deprecated 2010-02-05,Response to the EC’s RFI on Forums and Consortiums,https://www.iab.org/documents/correspondence-reports-documents/docs2010/response-to-the-ecs-rfi-on-forums-and-consortiums/,https://www.iab.org/wp-content/IAB-uploads/2011/03/2010-02-05-IAB-Response-Euro-ICT-Questionnaire.pdf 2011-03-30,IAB responds to NTIA Request for Comments on the IANA Functions,https://www.iab.org/documents/correspondence-reports-documents/2011-2/iab-responds-to-ntia-request-for-comments-on-the-iana-functions/,https://www.iab.org/wp-content/IAB-uploads/2011/04/2011-03-30-iab-iana-noi-response.pdf @@ -287,7 +306,7 @@ def load_spreadsheet(): ''' rows = [] - date_count = defaultdict(lambda:0) + date_count = defaultdict(lambda: 0) with io.StringIO(csv_dump) as csv_file: reader = csv.reader(csv_file) for row in reader: @@ -295,7 +314,7 @@ def load_spreadsheet(): if date_count[date] == 0: row.insert(0, "") else: - row.insert(0,date_count[date]) + row.insert(0, date_count[date]) date_count[date] += 1 rows.append(row) return rows diff --git a/ietf/doc/urls_statement.py b/ietf/doc/urls_statement.py index 50dc089bb09..e130c453ba8 100644 --- a/ietf/doc/urls_statement.py +++ b/ietf/doc/urls_statement.py @@ -6,5 +6,5 @@ urlpatterns = [ url(r"^(?:%(rev)s/)?pdf/$" % settings.URL_REGEXPS, views_statement.serve_pdf), - url(r'^submit/$', views_statement.submit), + url(r"^submit/$", views_statement.submit), ] From c669714a6f3fbfe6722d6faa06b5ae72e076fb4b Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 30 Jun 2023 13:08:30 -0500 Subject: [PATCH 18/19] feat: add edit/new buttons for the secretariat --- ietf/templates/doc/document_statement.html | 10 ++++++++++ ietf/templates/group/statements.html | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ietf/templates/doc/document_statement.html b/ietf/templates/doc/document_statement.html index a6ef59ec55e..7e6d5b94785 100644 --- a/ietf/templates/doc/document_statement.html +++ b/ietf/templates/doc/document_statement.html @@ -106,6 +106,16 @@ + {% if not snapshot %} + {% if request.user|has_role:"Secretariat" %} +

+ + Edit or upload revised statement text + +

+ {% endif %} +{% endif %}
diff --git a/ietf/templates/group/statements.html b/ietf/templates/group/statements.html index 574ed81e748..4e0fc615326 100644 --- a/ietf/templates/group/statements.html +++ b/ietf/templates/group/statements.html @@ -9,7 +9,15 @@ {% block group_content %} {% origin %}

{{group.acronym|upper}} Statements

- {% origin %} + {% if request.user|has_role:"Secretariat" %} + + {% endif %} From cbfc597cd590cdaf0b2af0f302734b538cbda93a Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 30 Jun 2023 13:25:38 -0500 Subject: [PATCH 19/19] fix: address PR #5895 review comments --- ietf/doc/views_statement.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ietf/doc/views_statement.py b/ietf/doc/views_statement.py index a07ed39fd22..0ffa6a3c4d0 100644 --- a/ietf/doc/views_statement.py +++ b/ietf/doc/views_statement.py @@ -19,6 +19,8 @@ from ietf.utils.text import xslugify from ietf.utils.textupload import get_cleaned_text_file_content +CONST_PDF_REV_NOTICE = "The current revision of this statement is in pdf format" + @cache_control(max_age=3600) def serve_pdf(self, name, rev=None): @@ -61,16 +63,13 @@ def require_field(f): "\r", "" ) default_content = render_to_string( - "doc/statement/statement_template.md", {"settings": settings} + "doc/statement/statement_template.md", {} ) if markdown_content == default_content: raise forms.ValidationError( "The example content may not be saved. Edit it to contain the next revision statement content." ) - if ( - markdown_content - == "The current revision of this statement is in pdf format" - ): + if markdown_content == CONST_PDF_REV_NOTICE: raise forms.ValidationError( "Not proceeding with the text noting that the current version is pdf. Did you mean to upload a new PDF?" ) @@ -146,7 +145,7 @@ def submit(request, name): else: if statement.uploaded_filename.endswith("pdf"): - text = "The current revision of this statement is in pdf format" + text = CONST_PDF_REV_NOTICE else: text = statement.text_or_error() init = {