From 73132b34f6dc4088a5a27977f68d8a05cbc38e03 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 13 Apr 2026 14:38:42 -0300 Subject: [PATCH 1/5] fix: resolve procmaterials when uploaded (#10693) * fix: resolve procmaterials when uploaded * fix: avoid leaving stale ResolvedMaterial --- ietf/meeting/utils.py | 16 ++++++++++++++-- ietf/meeting/views_proceedings.py | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py index bdf3d3d3d3..10ae0d3667 100644 --- a/ietf/meeting/utils.py +++ b/ietf/meeting/utils.py @@ -1025,9 +1025,18 @@ def resolve_materials_for_one_meeting(meeting: Meeting): ) def resolve_uploaded_material(meeting: Meeting, doc: Document): - resolved = [] + resolved: list[ResolvedMaterial] = [] + remove = ResolvedMaterial.objects.none() blob = resolve_one_material(doc, rev=None, ext=None) - if blob is not None: + if blob is None: + # Versionless file does not exist. Remove the versionless ResolvedMaterial + # if it existed. This is to avoid leaving behind a stale link to a replaced + # version. This comes up e.g. if a ProceedingsMaterial is changed from having + # an uploaded file to being an external URL. + remove = ResolvedMaterial.objects.filter( + name=doc.name, meeting_number=meeting.number + ) + else: resolved.append( ResolvedMaterial( name=doc.name, @@ -1047,12 +1056,15 @@ def resolve_uploaded_material(meeting: Meeting, doc: Document): blob=blob.name, ) ) + # Create the new record(s) ResolvedMaterial.objects.bulk_create( resolved, update_conflicts=True, unique_fields=["name", "meeting_number"], update_fields=["bucket", "blob"], ) + # and remove one if necessary (will be a none() queryset if not) + remove.delete() def store_blob_for_one_material_file(doc: Document, rev: str, filepath: Path): diff --git a/ietf/meeting/views_proceedings.py b/ietf/meeting/views_proceedings.py index d1169bff2d..639efa1da4 100644 --- a/ietf/meeting/views_proceedings.py +++ b/ietf/meeting/views_proceedings.py @@ -14,7 +14,7 @@ from ietf.meeting.models import Meeting, MeetingHost from ietf.meeting.helpers import get_meeting from ietf.name.models import ProceedingsMaterialTypeName -from ietf.meeting.utils import handle_upload_file +from ietf.meeting.utils import handle_upload_file, resolve_uploaded_material from ietf.utils.text import xslugify class UploadProceedingsMaterialForm(FileUploadForm): @@ -150,7 +150,7 @@ def save_proceedings_material_doc(meeting, material_type, title, request, file=N if events: doc.save_with_history(events) - + resolve_uploaded_material(meeting, doc) return doc From 3e23f162c4929703a630141a343d820252b29a83 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 13 Apr 2026 13:26:38 -0500 Subject: [PATCH 2/5] chore: git ignore .claude (#10705) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 84bc800e3b..ccc7a46b08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_store datatracker.sublime-project datatracker.sublime-workspace +/.claude /.coverage /.factoryboy_random_state /.mypy_cache From dee75e2e67bcb47b2dc1e556cc34ecd08dfa6806 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 13 Apr 2026 15:37:33 -0300 Subject: [PATCH 3/5] feat: unmask exceptions on blob retrieve error (#10704) These have not been encountered in at least three months, likely longer. Still remains to unmask exceptions when storing blobs. We are probably ready to do that, but log noise caused by expected failures makes it hard to be certain. Soon... --- ietf/doc/storage_utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ietf/doc/storage_utils.py b/ietf/doc/storage_utils.py index 81588c83ec..ffdd4599be 100644 --- a/ietf/doc/storage_utils.py +++ b/ietf/doc/storage_utils.py @@ -178,8 +178,7 @@ def retrieve_bytes(kind: str, name: str) -> bytes: content = f.read() except Exception as err: log(f"Blobstore Error: Failed to read bytes from {kind}:{name}: {repr(err)}") - if settings.SERVER_MODE == "development": - raise + raise return content @@ -192,6 +191,5 @@ def retrieve_str(kind: str, name: str) -> str: content = content_bytes.decode("utf-8") except Exception as err: log(f"Blobstore Error: Failed to read string from {kind}:{name}: {repr(err)}") - if settings.SERVER_MODE == "development": - raise + raise return content From aa847b4b8ae292e60f9d8f96bc23904c156cda47 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 13 Apr 2026 14:15:06 -0500 Subject: [PATCH 4/5] fix: look in target if draft name not found in anchor (#10636) --- ietf/utils/xmldraft.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ietf/utils/xmldraft.py b/ietf/utils/xmldraft.py index f555a0a16a..325b8499a9 100644 --- a/ietf/utils/xmldraft.py +++ b/ietf/utils/xmldraft.py @@ -102,6 +102,17 @@ def _document_name(self, ref): number = int(maybe_number) return f"{label}{number}" + target = ref.get("target") + if isinstance(target, str): + target = target.lower() + if target.startswith("https://datatracker.ietf.org/doc/"): + # len("https://datatracker.ietf.org/doc/")==33 + m = re.match(r"^(draft-[a-z0-9-]*[a-z0-9])([/-]\d{2})?/?$",target[33:]) + if m: + name = m.group(1) + return name + + # if we couldn't find a match so far, try the seriesInfo series_query = " or ".join(f"@name='{x.upper()}'" for x in series) for info in ref.xpath( From 60ecfa2029f81b6a6b987770a1edd4840a6acf02 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Mon, 13 Apr 2026 16:43:16 -0400 Subject: [PATCH 5/5] chore(k8s): remove interpod affinity --- k8s/auth.yaml | 10 ---------- k8s/beat.yaml | 10 ---------- k8s/celery.yaml | 10 ---------- k8s/memcached.yaml | 10 ---------- k8s/rabbitmq.yaml | 10 ---------- k8s/replicator.yaml | 10 ---------- 6 files changed, 60 deletions(-) diff --git a/k8s/auth.yaml b/k8s/auth.yaml index 392e306b54..2bdb064447 100644 --- a/k8s/auth.yaml +++ b/k8s/auth.yaml @@ -15,16 +15,6 @@ spec: labels: app: auth spec: - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - datatracker - topologyKey: "kubernetes.io/hostname" securityContext: runAsNonRoot: true containers: diff --git a/k8s/beat.yaml b/k8s/beat.yaml index cc98beecf6..9ab242681c 100644 --- a/k8s/beat.yaml +++ b/k8s/beat.yaml @@ -17,16 +17,6 @@ spec: labels: app: beat spec: - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - datatracker - topologyKey: "kubernetes.io/hostname" securityContext: runAsNonRoot: true containers: diff --git a/k8s/celery.yaml b/k8s/celery.yaml index a2799f2a6d..2f4c0fd439 100644 --- a/k8s/celery.yaml +++ b/k8s/celery.yaml @@ -17,16 +17,6 @@ spec: labels: app: celery spec: - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - datatracker - topologyKey: "kubernetes.io/hostname" securityContext: runAsNonRoot: true containers: diff --git a/k8s/memcached.yaml b/k8s/memcached.yaml index 8f73f3d0d5..5a4c9f0aed 100644 --- a/k8s/memcached.yaml +++ b/k8s/memcached.yaml @@ -13,16 +13,6 @@ spec: labels: app: memcached spec: - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - datatracker - topologyKey: "kubernetes.io/hostname" securityContext: runAsNonRoot: true containers: diff --git a/k8s/rabbitmq.yaml b/k8s/rabbitmq.yaml index 780a399239..346b54c93e 100644 --- a/k8s/rabbitmq.yaml +++ b/k8s/rabbitmq.yaml @@ -13,16 +13,6 @@ spec: labels: app: rabbitmq spec: - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - datatracker - topologyKey: "kubernetes.io/hostname" securityContext: runAsNonRoot: true containers: diff --git a/k8s/replicator.yaml b/k8s/replicator.yaml index 9c462bd96b..a28d9e8a16 100644 --- a/k8s/replicator.yaml +++ b/k8s/replicator.yaml @@ -17,16 +17,6 @@ spec: labels: app: replicator spec: - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - datatracker - topologyKey: "kubernetes.io/hostname" securityContext: runAsNonRoot: true containers: