diff --git a/ietf/doc/views_search.py b/ietf/doc/views_search.py index 0117886f70..4bde5b0dd8 100644 --- a/ietf/doc/views_search.py +++ b/ietf/doc/views_search.py @@ -39,7 +39,9 @@ import copy import operator +from collections import defaultdict from functools import reduce + from django import forms from django.conf import settings from django.core.cache import cache, caches @@ -360,32 +362,52 @@ def cached_redirect(cache_key, url): return cached_redirect(cache_key, urlreverse('ietf.doc.views_search.search') + search_args) -def state_name(doc_type, state, shorten=True): - name = "" - # Note doc_type rfc here is _not_ necessarily Document.type - for some callers - # it is a type derived from draft... The ad_workload view needs more rework so that - # the code isn't having to shadow-box so much. - if doc_type == "rfc": - if state == "rfc": - name = "RFC" - if name == "": - s = State.objects.filter(type="rfc",slug=state).first() - if s: - name = s.name - if name == "": - name = State.objects.get(type__in=["draft", "draft-iesg"], slug=state).name - elif doc_type == "draft" and state not in ["rfc", "expired"]: - name = State.objects.get(type__in=["draft", "draft-iesg"], slug=state).name - elif doc_type == "draft" and state == "rfc": - name = "RFC" - elif doc_type == "conflrev" and state.startswith("appr"): - name = "Approved" - else: - name = State.objects.get(type=doc_type, slug=state).name +def get_state_name_calculator(): + """Get a function to calculate state names + + Queries the database once when called, then uses cached look-up table for name calculations. + """ + # state_lut always has at least rfc, draft, and draft-iesg keys + state_lut = defaultdict(dict, **{"rfc": {}, "draft":{}, "draft-iesg": {}}) + for state in State.objects.filter(used=True): + state_lut[state.type_id][state.slug] = state.name + state_lut = dict(state_lut) # convert to dict to freeze key changes + + def _get_state_name(doc_type, state_slug): + """Get display name for a doc type / state slug + + Note doc_type rfc here is _not_ necessarily Document.type - for some callers + it is a type derived from draft... The ad_workload view needs more rework so that + the code isn't having to shadow-box so much. + """ + if doc_type == "rfc": + if state_slug == "rfc": + return "RFC" + elif state_slug in state_lut["rfc"]: + return state_lut["rfc"][state_slug] + else: + return state_lut["draft"].get( + state_slug, + state_lut["draft-iesg"][state_slug], + ) + elif doc_type == "draft" and state_slug not in ["rfc", "expired"]: + return state_lut["draft"].get( + state_slug, + state_lut["draft-iesg"][state_slug], + ) + elif doc_type == "draft" and state_slug == "rfc": + return "RFC" + elif doc_type == "conflrev" and state_slug.startswith("appr"): + return "Approved" + else: + return state_lut[doc_type][state_slug] + + # return the function as a closure + return _get_state_name - if not shorten: - return name +def shorten_state_name(name): + """Get abbreviated display name for a state""" for pat, sub in [ (r" \(Internal Steering Group/IAB Review\)", ""), ("Writeup", "Write-up"), @@ -412,31 +434,27 @@ def state_name(doc_type, state, shorten=True): (r"\(Message to Community, Selected by Secretariat\)", ""), ]: name = re.sub(pat, sub, name) - return name.strip() -STATE_SLUGS = { - dt: {state_name(dt, ds, shorten=False): ds for ds in AD_WORKLOAD[dt]} # type: ignore - for dt in AD_WORKLOAD -} - - -def state_to_doc_type(state): - for dt in STATE_SLUGS: - if state in STATE_SLUGS[dt]: - return dt - return None - - -IESG_STATES = State.objects.filter(type="draft-iesg").values_list("name", flat=True) - - def date_to_bucket(date, now, num_buckets): return num_buckets - int((now.date() - date.date()).total_seconds() / 60 / 60 / 24) def ad_workload(request): + _calculate_state_name = get_state_name_calculator() + IESG_STATES = State.objects.filter(type="draft-iesg").values_list("name", flat=True) + STATE_SLUGS = { + dt: {_calculate_state_name(dt, ds): ds for ds in AD_WORKLOAD[dt]} # type: ignore + for dt in AD_WORKLOAD.keys() + } + + def _state_to_doc_type(state): + for dt in STATE_SLUGS: + if state in STATE_SLUGS[dt]: + return dt + return None + # number of days (= buckets) to show in the graphs days = 120 if has_role(request.user, ["Area Director", "Secretariat"]) else 1 now = timezone.now() @@ -483,7 +501,7 @@ def ad_workload(request): to_state = None if dt == "charter": if e.type == "closed_ballot": - to_state = state_name(dt, state, shorten=False) + to_state = _calculate_state_name(dt, state) elif e.desc.endswith("has been replaced"): # stop tracking last = e.time @@ -512,7 +530,7 @@ def ad_workload(request): to_state = "RFC" if dt == "rfc": - new_dt = state_to_doc_type(to_state) + new_dt = _state_to_doc_type(to_state) if new_dt is not None and new_dt != dt: dt = new_dt @@ -549,7 +567,9 @@ def ad_workload(request): metadata = [ { "type": (dt, doc_type_name(dt)), - "states": [(state, state_name(dt, state)) for state in ad.buckets[dt]], + "states": [ + (state, shorten_state_name(_calculate_state_name(dt, state))) for state in ad.buckets[dt] + ], "ads": ads, } for dt in AD_WORKLOAD @@ -616,9 +636,10 @@ def sort_key(doc): ) ] + _calculate_state_name = get_state_name_calculator() for d in results: dt = d.type.slug - d.search_heading = state_name(dt, doc_state(d), shorten=False) + d.search_heading = _calculate_state_name(dt, doc_state(d)) if d.search_heading != "RFC": d.search_heading += f" {doc_type_name(dt)}"