From 0390f1868f4c5121db35f56d518e74d9c931f481 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 21 Sep 2023 12:54:17 -0500 Subject: [PATCH] feat: subseries --- ietf/doc/migrations/0018_subseries.py | 21 +++++++ ietf/doc/models.py | 6 ++ ietf/doc/urls.py | 2 + ietf/doc/views_doc.py | 20 +++---- ietf/doc/views_search.py | 8 +++ ietf/name/migrations/0010_subseries.py | 38 +++++++++++++ ietf/sync/rfceditor.py | 55 +++++++++++++++---- ietf/templates/doc/document_history.html | 37 ++++++++----- ietf/templates/doc/document_subseries.html | 13 +++++ .../templates/doc/document_subseries_top.html | 17 ++++++ ietf/templates/doc/index_subseries.html | 19 +++++++ 11 files changed, 200 insertions(+), 36 deletions(-) create mode 100644 ietf/doc/migrations/0018_subseries.py create mode 100644 ietf/name/migrations/0010_subseries.py create mode 100644 ietf/templates/doc/document_subseries.html create mode 100644 ietf/templates/doc/document_subseries_top.html create mode 100644 ietf/templates/doc/index_subseries.html diff --git a/ietf/doc/migrations/0018_subseries.py b/ietf/doc/migrations/0018_subseries.py new file mode 100644 index 0000000000..06028b9fa1 --- /dev/null +++ b/ietf/doc/migrations/0018_subseries.py @@ -0,0 +1,21 @@ +# Copyright The IETF Trust 2023, All Rights Reserved +from django.db import migrations + + +def forward(apps, schema_editor): + StateType = apps.get_model("doc", "StateType") + for slug in ["bcp", "std", "fyi"]: + StateType.objects.create(slug=slug, label=f"{slug} state") + + +def reverse(apps, schema_editor): + StateType = apps.get_model("doc", "StateType") + StateType.objects.filter(slug__in=["bcp", "std", "fyi"]).delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("doc", "0017_move_dochistory"), + ] + + operations = [migrations.RunPython(forward, reverse)] diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 9da3ebf4eb..0f7eb24449 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -673,6 +673,12 @@ def came_from_draft(self): doc = self if isinstance(self, Document) else self.doc self._cached_came_from_draft = next(iter(doc.related_that("became_rfc")), None) return self._cached_came_from_draft + + def contains(self): + return self.related_that_doc("contains") + + def part_of(self): + return self.related_that("contains") class Meta: abstract = True diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index b55501671e..cdd4e4fa6e 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -94,6 +94,8 @@ url(r'^ballots/irsg/$', views_ballot.irsg_ballot_status), url(r'^ballots/rsab/$', views_ballot.rsab_ballot_status), + url(r'^(?P(bcp|std|fyi))/?$', views_search.index_subseries), + url(r'^%(name)s(?:/%(rev)s)?/$' % settings.URL_REGEXPS, views_doc.document_main), url(r'^%(name)s(?:/%(rev)s)?/bibtex/$' % settings.URL_REGEXPS, views_doc.document_bibtex), url(r'^%(name)s(?:/%(rev)s)?/idnits2-state/$' % settings.URL_REGEXPS, views_doc.idnits2_state), diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 9811173e2d..8bca007f1c 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -154,8 +154,8 @@ def render_document_top(request, doc, tab, name): None, ) ) - - tabs.append(("Email expansions","email",urlreverse('ietf.doc.views_doc.document_email', kwargs=dict(name=name)), True, None)) + if not doc.type_id in ["bcp", "std", "fyi"]: + tabs.append(("Email expansions","email",urlreverse('ietf.doc.views_doc.document_email', kwargs=dict(name=name)), True, None)) tabs.append(("History", "history", urlreverse('ietf.doc.views_doc.document_history', kwargs=dict(name=name)), True, None)) if name.startswith("rfc"): @@ -163,7 +163,7 @@ def render_document_top(request, doc, tab, name): else: name += "-" + doc.rev - return render_to_string("doc/document_top.html", + return render_to_string("doc/document_top.html" if not doc.type_id in ["bcp", "std", "fyi"] else "doc/document_subseries_top.html", dict(doc=doc, tabs=tabs, selected=tab, @@ -934,7 +934,7 @@ def document_main(request, name, rev=None, document_html=False): ) ) - if doc.type_id == "statement": + elif doc.type_id == "statement": if doc.uploaded_filename: basename = doc.uploaded_filename.split(".")[0] # strip extension else: @@ -955,7 +955,6 @@ def document_main(request, name, rev=None, document_html=False): 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, @@ -968,6 +967,9 @@ def document_main(request, name, rev=None, document_html=False): replaced_by=interesting_relations_that.filter(relationship="replaces"), can_manage=can_manage, )) + elif doc.type_id in ["bcp", "std", "fyi"]: + return render(request, "doc/document_subseries.html", {"doc": doc, "top": top}) + raise Http404("Document not found: %s" % (name + ("-%s"%rev if rev else ""))) @@ -1230,13 +1232,6 @@ def document_history(request, name): request.user, ("Area Director", "Secretariat", "IRTF Chair") ) - # Get related docs whose history should be linked - if doc.type_id == "draft": - related = doc.related_that_doc("became_rfc") - elif doc.type_id == "rfc": - related = doc.related_that("became_rfc") - else: - related = [] return render( request, @@ -1246,7 +1241,6 @@ def document_history(request, name): "top": top, "diff_revisions": diff_revisions, "events": events, - "related": related, "can_add_comment": can_add_comment, }, ) diff --git a/ietf/doc/views_search.py b/ietf/doc/views_search.py index 7b33434c55..c1cc718835 100644 --- a/ietf/doc/views_search.py +++ b/ietf/doc/views_search.py @@ -895,3 +895,11 @@ def ajax_select2_search_docs(request, model_name, doc_type): # TODO - remove mod objs = qs.distinct().order_by("name")[:20] return HttpResponse(select2_id_doc_name_json(model, objs), content_type='application/json') + +def index_subseries(request, type_id): + docs = sorted(Document.objects.filter(type_id=type_id),key=lambda o: int(o.name[3:])) + if len(docs)>0: + type = docs[0].type + else: + type = DocTypeName.objects.get(slug=type_id) + return render(request, "doc/index_subseries.html", {"type": type, "docs": docs}) diff --git a/ietf/name/migrations/0010_subseries.py b/ietf/name/migrations/0010_subseries.py new file mode 100644 index 0000000000..21a1a6a09d --- /dev/null +++ b/ietf/name/migrations/0010_subseries.py @@ -0,0 +1,38 @@ +# Copyright The IETF Trust 2023, All Rights Reserved + +from django.db import migrations + + +def forward(apps, schema_editor): + DocTypeName = apps.get_model("name", "DocTypeName") + DocRelationshipName = apps.get_model("name", "DocRelationshipName") + for slug, name, prefix in [ + ("std", "Standard", "std"), + ("bcp", "Best Current Practice", "bcp"), + ("fyi", "For Your Information", "fyi"), + ]: + DocTypeName.objects.create( + slug=slug, name=name, prefix=prefix, desc="", used=True + ) + DocRelationshipName.objects.create( + slug="contains", + name="Contains", + revname="Is part of", + desc="This document contains other documents (e.g., STDs contain RFCs)", + used=True, + ) + + +def reverse(apps, schema_editor): + DocTypeName = apps.get_model("name", "DocTypeName") + DocRelationshipName = apps.get_model("name", "DocRelationshipName") + DocTypeName.objects.filter(slug__in=["std", "bcp", "fyi"]).delete() + DocRelationshipName.objects.filter(slug="contains").delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("name", "0009_rfc_doctype_names"), + ] + + operations = [migrations.RunPython(forward, reverse)] diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py index 557df481e5..99b649c646 100644 --- a/ietf/sync/rfceditor.py +++ b/ietf/sync/rfceditor.py @@ -380,6 +380,8 @@ def update_docs_from_rfc_index( system = Person.objects.get(name="(System)") + first_sync_creating_subseries = not Document.objects.filter(type_id__in=["bcp","std","fyi"]).exists() + for ( rfc_number, title, @@ -511,8 +513,9 @@ def update_docs_from_rfc_index( stream_slug = f"draft-stream-{draft.stream.slug}" prev_state = draft.get_state(stream_slug) if prev_state is None: + # TODO: main returns warnings to the caller rather tha logging to the system - look to see if we should be using that instead log(f"Warning while processing {doc.name}: draft {draft.name} stream state was not set") - if prev_state.slug != "pub": + elif prev_state.slug != "pub": new_state = State.objects.select_related("type").get(used=True, type__slug=stream_slug, slug="pub") draft.set_state(new_state) draft_changes.append( @@ -650,15 +653,47 @@ def parse_relation_list(l): ) ) - # This block attempted to alias subseries names to RFCs. - # Handle that differently when we add subseries as a document type. - # - # if also: - # for a in also: - # a = a.lower() - # if not DocAlias.objects.filter(name=a): - # DocAlias.objects.create(name=a).docs.add(doc) - # rfc_changes.append(f"created alias {prettify_std_name(a)}") + if also: + # recondition also to have proper subseries document names: + conditioned_also = [] + for a in also: + a = a.lower() + subseries_slug = a[:3] + if subseries_slug not in ["bcp", "std", "fyi"]: + log(f"Unexpected 'also' relationship of {a} encountered for {doc}") + next + maybe_number = a[3:].strip() + if not maybe_number.isdigit(): + log(f"Unexpected 'also' subseries element identifier {a} encountered for {doc}") + next + else: + subseries_number = int(maybe_number) + conditioned_also.append(f"{subseries_slug}{subseries_number}") # Note the lack of leading zeros + also = conditioned_also + + for a in also: + subseries_doc_name = a + subseries_slug=a[:3] + # Leaving most things to the default intentionally + # Of note, title and stream are left to the defaults of "" and none. + subseries_doc, created = Document.objects.get_or_create(type_id=subseries_slug, name=subseries_doc_name) + if created: + if first_sync_creating_subseries: + subseries_doc.docevent_set.create(type=f"{subseries_slug}_history_marker", by=system, desc=f"No history of this {subseries_slug.upper()} document is currently available in the datatracker before this point") + subseries_doc.docevent_set.create(type="subseries_doc_created", by=system, desc=f"Created {subseries_doc_name} via sync to the rfc-index") + if not subseries_doc.relateddocument_set.filter(relationship_id="contains", target=doc).exists(): + subseries_doc.relateddocument_set.create(relationship_id="contains", target=doc) + subseries_doc.docevent_set.create(type="sync_from_rfc_editor", by=system, desc=f"Added {doc.name} to {subseries_doc.name}") + if first_sync_creating_subseries: + rfc_events.append(doc.docevent_set.create(type=f"{subseries_slug}_history_marker", by=system, desc=f"No history of {subseries_doc.name.upper()} is currently available in the datatracker before this point")) + rfc_events.append(doc.docevent_set.create(type="sync_from_rfc_editor", by=system, desc=f"Added {doc.name} to {subseries_doc.name}")) + + for subdoc in doc.related_that("contains"): + if subdoc.name not in also: + assert(not first_sync_creating_subseries) + subseries_doc.relateddocument_set.filter(target=subdoc).delete() + rfc_events.append(doc.docevent_set.create(type="sync_from_rfc_editor", by=system, desc=f"Removed {doc.name} from {subseries_doc.name}")) + subseries_doc.docevent_set.create(type="sync_from_rfc_editor", by=system, desc=f"Removed {doc.name} from {subseries_doc.name}") doc_errata = errata.get(f"RFC{rfc_number}", []) all_rejected = doc_errata and all( diff --git a/ietf/templates/doc/document_history.html b/ietf/templates/doc/document_history.html index c41715f749..a37dd4dde7 100644 --- a/ietf/templates/doc/document_history.html +++ b/ietf/templates/doc/document_history.html @@ -20,17 +20,28 @@

Revision differences

{% include "doc/document_history_form.html" with doc=doc diff_revisions=diff_revisions action=rfcdiff_base_url snapshot=snapshot only %} {% endif %} -

Document history - {% if related %} -
- {% for related_document in related %} - - Related history for {{ related_document.name }} - - {% endfor %} -
- {% endif %}

+

Document history

+ {% if doc.came_from_draft %} + + {% endif %} + {% if doc.became_rfc %} + + {% endif %} + {% if can_add_comment %}
Document history Date - {% if doc.type_id != "rfc" %}Rev.{% endif %} + {% if doc.type_id not in "rfc,bcp,std,fyi" %}Rev.{% endif %} By Action @@ -55,7 +66,7 @@

Document history
{{ e.time|date:"Y-m-d" }}
- {% if doc.type_id != "rfc" %}{{ e.rev }}{% endif %} + {% if doc.type_id not in "rfc,bcp,std,fyi" %}{{ e.rev }}{% endif %} {{ e.by|escape }} {{ e.desc|format_history_text }} diff --git a/ietf/templates/doc/document_subseries.html b/ietf/templates/doc/document_subseries.html new file mode 100644 index 0000000000..d98b568db8 --- /dev/null +++ b/ietf/templates/doc/document_subseries.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2023, All Rights Reserved #} +{% load origin %} +{% load static %} +{% block title %}{{doc.type.name}}s{% endblock %} +{% block content %} + {% origin %} + {{ top|safe }} +

{{ doc.name|slice:":3"|upper }} {{ doc.name|slice:"3:"}} consists of:

+ {% for rfc in doc.contains %} +

RFC {{rfc.name|slice:"3:"}} : {{rfc.title}}

+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/ietf/templates/doc/document_subseries_top.html b/ietf/templates/doc/document_subseries_top.html new file mode 100644 index 0000000000..f7c8ba8e45 --- /dev/null +++ b/ietf/templates/doc/document_subseries_top.html @@ -0,0 +1,17 @@ +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} +{% origin %} +{% load ietf_filters %} +

+ {{ doc.name|slice:":3"|upper }} {{ doc.name|slice:"3:"}} +

+ diff --git a/ietf/templates/doc/index_subseries.html b/ietf/templates/doc/index_subseries.html new file mode 100644 index 0000000000..f87d7722ab --- /dev/null +++ b/ietf/templates/doc/index_subseries.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2023, All Rights Reserved #} +{% load origin %} +{% load static %} +{% block title %}{{type.name}}s{% endblock %} +{% block content %} + {% origin %} +

{{type.name}}s

+ {% for doc in docs %} +
+ +
+ {% for rfc in doc.contains %} +

{{rfc.name}} : {{rfc.title}}

+ {% endfor %} +
+
+ {% endfor %} +{% endblock %} \ No newline at end of file