From 2ea66d05d108240387ef4dc1867fe4196d2a1864 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Apr 2026 17:32:25 +0000 Subject: [PATCH] Move tag slug logic onto Tag.slug @property Replaces the tag_filename_safe() helper with a Tag.slug property that returns the slugified form via django.utils.text.slugify. Call sites now just use tag.slug directly. --- archivebox/api/v1_core.py | 5 ++--- archivebox/core/models.py | 6 ++++++ archivebox/core/tag_utils.py | 6 ------ archivebox/tests/test_tag_admin.py | 8 ++------ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/archivebox/api/v1_core.py b/archivebox/api/v1_core.py index 8e517f14..d1834de1 100644 --- a/archivebox/api/v1_core.py +++ b/archivebox/api/v1_core.py @@ -36,7 +36,6 @@ from archivebox.core.tag_utils import ( normalize_has_snapshots_filter, normalize_tag_sort, rename_tag as rename_tag_record, - tag_filename_safe, ) from archivebox.crawls.models import Crawl from archivebox.api.v1_crawls import CrawlSchema @@ -725,7 +724,7 @@ def tag_urls_export(request: HttpRequest, tag_id: int): raise HttpError(404, "Tag not found") from err response = HttpResponse(export_tag_urls(tag), content_type="text/plain; charset=utf-8") - response["Content-Disposition"] = f'attachment; filename="tag-{tag_filename_safe(tag.name)}-urls.txt"' + response["Content-Disposition"] = f'attachment; filename="tag-{tag.slug}-urls.txt"' return response @@ -737,7 +736,7 @@ def tag_snapshots_export(request: HttpRequest, tag_id: int): raise HttpError(404, "Tag not found") from err response = HttpResponse(export_tag_snapshots_jsonl(tag), content_type="application/x-ndjson; charset=utf-8") - response["Content-Disposition"] = f'attachment; filename="tag-{tag_filename_safe(tag.name)}-snapshots.jsonl"' + response["Content-Disposition"] = f'attachment; filename="tag-{tag.slug}-snapshots.jsonl"' return response diff --git a/archivebox/core/models.py b/archivebox/core/models.py index ebb892c6..7af1176e 100755 --- a/archivebox/core/models.py +++ b/archivebox/core/models.py @@ -15,6 +15,7 @@ from statemachine import State, registry from django.db import models from django.db.models import QuerySet from django.utils.functional import cached_property +from django.utils.text import slugify from django.utils import timezone from django.core.cache import cache from django.urls import reverse_lazy @@ -69,6 +70,11 @@ class Tag(ModelWithUUID): def __str__(self): return self.name + @property + def slug(self) -> str: + """ASCII-safe slugified form of the tag name (derived, not stored).""" + return slugify(self.name or "") or "tag" + @property def api_url(self) -> str: return str(reverse_lazy("api-1:get_tag", args=[self.id])) diff --git a/archivebox/core/tag_utils.py b/archivebox/core/tag_utils.py index 4815ec3b..d27e7f56 100644 --- a/archivebox/core/tag_utils.py +++ b/archivebox/core/tag_utils.py @@ -10,7 +10,6 @@ from django.db.models import Count, F, QuerySet from django.db.models.functions import Lower from django.http import HttpRequest from django.urls import reverse -from django.utils.text import slugify from archivebox.core.host_utils import build_snapshot_url, build_web_url from archivebox.core.models import Snapshot, SnapshotTag, Tag @@ -36,11 +35,6 @@ def normalize_tag_name(name: str) -> str: return (name or "").strip() -def tag_filename_safe(name: str) -> str: - """ASCII-safe filename fragment for a tag name (via django.utils.text.slugify).""" - return slugify(name or "") or "tag" - - def normalize_tag_sort(sort: str = "created_desc") -> str: valid_sorts = {key for key, _label in TAG_SORT_CHOICES} return sort if sort in valid_sorts else "created_desc" diff --git a/archivebox/tests/test_tag_admin.py b/archivebox/tests/test_tag_admin.py index e39b1538..436bc46a 100644 --- a/archivebox/tests/test_tag_admin.py +++ b/archivebox/tests/test_tag_admin.py @@ -179,11 +179,9 @@ def test_tag_snapshots_export_returns_jsonl(client, api_token, tagged_data): HTTP_HOST=ADMIN_HOST, ) - from archivebox.core.tag_utils import tag_filename_safe - assert response.status_code == 200 assert response["Content-Type"].startswith("application/x-ndjson") - assert f"tag-{tag_filename_safe(tag.name)}-snapshots.jsonl" in response["Content-Disposition"] + assert f"tag-{tag.slug}-snapshots.jsonl" in response["Content-Disposition"] body = response.content.decode() assert '"type": "Snapshot"' in body assert '"tags": "Alpha Research"' in body @@ -198,10 +196,8 @@ def test_tag_urls_export_returns_plain_text_urls(client, api_token, tagged_data) HTTP_HOST=ADMIN_HOST, ) - from archivebox.core.tag_utils import tag_filename_safe - assert response.status_code == 200 assert response["Content-Type"].startswith("text/plain") - assert f"tag-{tag_filename_safe(tag.name)}-urls.txt" in response["Content-Disposition"] + assert f"tag-{tag.slug}-urls.txt" in response["Content-Disposition"] exported_urls = set(filter(None, response.content.decode().splitlines())) assert exported_urls == {snapshot.url for snapshot in snapshots}