diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 4d7a4f805c..fdda23ecd1 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -700,6 +700,8 @@ def is_downref(self): if self.source.type_id == "rfc": source_lvl = self.source.std_level_id + elif self.source.type_id in ["bcp","std"]: + source_lvl = self.source.type_id else: source_lvl = self.source.intended_std_level_id @@ -711,6 +713,8 @@ def is_downref(self): target_lvl = 'unkn' else: target_lvl = self.target.std_level_id + elif self.target.type_id in ["bcp", "std"]: + target_lvl = self.target.type_id else: if not self.target.intended_std_level: target_lvl = 'unkn' diff --git a/ietf/doc/tests_utils.py b/ietf/doc/tests_utils.py index 59223b1f8d..248ac345af 100644 --- a/ietf/doc/tests_utils.py +++ b/ietf/doc/tests_utils.py @@ -309,6 +309,12 @@ def do_fuzzy_find_documents_rfc_test(self, name): found = fuzzy_find_documents(draft.name, '22') self.assertCountEqual(found.documents, [draft], 'Should find document even if rev does not exist') + + # by rfc name mistakenly trying to provide a revision + found = fuzzy_find_documents(rfc.name+"-22") + self.assertCountEqual(found.documents, [rfc], "Should ignore versions when fuzzyfinding RFCs" ) + found = fuzzy_find_documents(rfc.name,"22") + self.assertCountEqual(found.documents, [rfc], "Should ignore versions when fuzzyfinding RFCs" ) def test_fuzzy_find_documents(self): diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index 791339c4fc..d93c9f4531 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -1209,14 +1209,15 @@ def fuzzy_find_documents(name, rev=None): if name.startswith("rfc"): sought_type = "rfc" - log.assertion("rev is None") + name = name.split("-")[0] # strip any noise (like a revision) at and after the first hyphen + rev = None # If someone is looking for an RFC and supplies a version, ignore it. else: sought_type = "draft" # see if we can find a document using this name docs = Document.objects.filter(name=name, type_id=sought_type) - if rev and not docs.exists(): - # No document found, see if the name/rev split has been misidentified. + if sought_type == "draft" and rev and not docs.exists(): + # No draft found, see if the name/rev split has been misidentified. # Handles some special cases, like draft-ietf-tsvwg-ieee-802-11. name = '%s-%s' % (name, rev) docs = Document.objects.filter(name=name, type_id='draft') diff --git a/ietf/group/views.py b/ietf/group/views.py index f2abe73caa..991a1b8d87 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -740,14 +740,31 @@ def dependencies(request, acronym, group_type=None): source__type="draft", relationship__slug__startswith="ref", ) - - both_rfcs = Q(source__type_id="rfc", target__type_id="rfc") - inactive = Q(source__states__slug__in=["expired", "repl"]) + rfc_or_subseries = {"rfc", "bcp", "fyi", "std"} + both_rfcs = Q(source__type_id="rfc", target__type_id__in=rfc_or_subseries) + pre_rfc_draft_to_rfc = Q( + source__states__type="draft", + source__states__slug="rfc", + target__type_id__in=rfc_or_subseries, + ) + both_pre_rfcs = Q( + source__states__type="draft", + source__states__slug="rfc", + target__type_id="draft", + target__states__type="draft", + target__states__slug="rfc", + ) + inactive = Q( + source__states__type="draft", + source__states__slug__in=["expired", "repl"], + ) attractor = Q(target__name__in=["rfc5000", "rfc5741"]) - removed = Q(source__states__slug__in=["auth-rm", "ietf-rm"]) + removed = Q(source__states__type="draft", source__states__slug__in=["auth-rm", "ietf-rm"]) relations = ( RelatedDocument.objects.filter(references) .exclude(both_rfcs) + .exclude(pre_rfc_draft_to_rfc) + .exclude(both_pre_rfcs) .exclude(inactive) .exclude(attractor) .exclude(removed) @@ -755,8 +772,8 @@ def dependencies(request, acronym, group_type=None): links = set() for x in relations: - target_state = x.target.get_state_slug("draft") - if target_state != "rfc" or x.is_downref(): + always_include = x.target.type_id not in rfc_or_subseries and x.target.get_state_slug("draft") != "rfc" + if always_include or x.is_downref(): links.add(x) replacements = RelatedDocument.objects.filter( @@ -771,13 +788,12 @@ def dependencies(request, acronym, group_type=None): graph = { "nodes": [ { - "id": x.name, - "rfc": x.get_state("draft").slug == "rfc", - "post-wg": not x.get_state("draft-iesg").slug - in ["idexists", "watching", "dead"], - "expired": x.get_state("draft").slug == "expired", - "replaced": x.get_state("draft").slug == "repl", - "group": x.group.acronym if x.group.acronym != "none" else "", + "id": x.became_rfc().name if x.became_rfc() else x.name, + "rfc": x.type_id == "rfc" or x.became_rfc() is not None, + "post-wg": x.get_state_slug("draft-iesg") not in ["idexists", "watching", "dead"], + "expired": x.get_state_slug("draft") == "expired", + "replaced": x.get_state_slug("draft") == "repl", + "group": x.group.acronym if x.group and x.group.acronym != "none" else "", "url": x.get_absolute_url(), "level": x.intended_std_level.name if x.intended_std_level @@ -789,8 +805,8 @@ def dependencies(request, acronym, group_type=None): ], "links": [ { - "source": x.source.name, - "target": x.target.name, + "source": x.source.became_rfc().name if x.source.became_rfc() else x.source.name, + "target": x.target.became_rfc().name if x.target.became_rfc() else x.target.name, "rel": "downref" if x.is_downref() else x.relationship.slug, } for x in links diff --git a/ietf/stats/views.py b/ietf/stats/views.py index e2b7706a26..23e90ea23c 100644 --- a/ietf/stats/views.py +++ b/ietf/stats/views.py @@ -14,7 +14,7 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.cache import cache -from django.db.models import Count, Q +from django.db.models import Count, Q, Subquery, OuterRef from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse as urlreverse @@ -34,7 +34,7 @@ from ietf.person.models import Person from ietf.name.models import ReviewResultName, CountryName, DocRelationshipName, ReviewAssignmentStateName from ietf.person.name import plain_name -from ietf.doc.models import Document, State, DocEvent +from ietf.doc.models import Document, RelatedDocument, State, DocEvent from ietf.meeting.models import Meeting from ietf.stats.models import MeetingRegistration, CountryAlias from ietf.stats.utils import get_aliased_affiliations, get_aliased_countries, compute_hirsch_index @@ -607,15 +607,31 @@ def build_document_stats_url(stats_type_override=Ellipsis, get_overrides=None): doc_years = defaultdict(set) - docevent_qs = DocEvent.objects.filter( + draftevent_qs = DocEvent.objects.filter( doc__type="draft", - type__in=["published_rfc", "new_revision"], - ).values_list("doc", "time").order_by("doc") + type = "new_revision", + ).values_list("doc","time").order_by("doc") - for doc_id, time in docevent_qs.iterator(): + for doc_id, time in draftevent_qs.iterator(): # RPC_TZINFO is used to match the timezone handling in Document.pub_date() doc_years[doc_id].add(time.astimezone(RPC_TZINFO).year) + rfcevent_qs = ( + DocEvent.objects.filter(doc__type="rfc", type="published_rfc") + .annotate( + draft=Subquery( + RelatedDocument.objects.filter( + target=OuterRef("doc__pk"), relationship_id="became_rfc" + ).values_list("source", flat=True)[:1] + ) + ) + .values_list("draft", "time") + .order_by("draft") + ) + + for doc_id, time in rfcevent_qs.iterator(): + doc_years[doc_id].add(time.astimezone(RPC_TZINFO).year) + person_qs = Person.objects.filter(person_filters) if document_type == "rfc":