diff --git a/archivebox/base_models/models.py b/archivebox/base_models/models.py
index 296bf741..b50bf521 100755
--- a/archivebox/base_models/models.py
+++ b/archivebox/base_models/models.py
@@ -284,16 +284,20 @@ class ModelWithOutputDir(ModelWithUUID):
elif path.is_dir():
shutil.rmtree(path, ignore_errors=True)
+ def schedule_delete_cleanup(self, *, using: str | None = None) -> None:
+ """Capture output paths before DB deletion and remove them after commit."""
+ paths = self.validate_output_paths_for_delete(self.output_paths_for_delete())
+ transaction.on_commit(lambda: self.delete_output_paths(paths), using=using)
+
@classmethod
def register_delete_signal(cls) -> None:
if cls._delete_signal_registered:
return
- def schedule_output_dir_cleanup(sender, instance, **kwargs):
+ def schedule_output_dir_cleanup(sender, instance, using, **kwargs):
if not isinstance(instance, ModelWithOutputDir):
return
- paths = instance.validate_output_paths_for_delete(instance.output_paths_for_delete())
- transaction.on_commit(lambda paths=paths: instance.delete_output_paths(paths))
+ instance.schedule_delete_cleanup(using=using)
pre_delete.connect(
schedule_output_dir_cleanup,
diff --git a/archivebox/core/models.py b/archivebox/core/models.py
index 231afe61..22e730f9 100644
--- a/archivebox/core/models.py
+++ b/archivebox/core/models.py
@@ -3300,7 +3300,7 @@ class Snapshot(ModelWithDeleteAfter, ModelWithOutputDir, ModelWithConfig, ModelW
fallback_ts = ts_to_date_str(self.downloaded_at or self.created_at)
for root, root_entries in grouped_hash_outputs.items():
fallback_path = ArchiveResult._fallback_output_file_path(list(root_entries.keys()), root, root_entries)
- if not fallback_path:
+ if not fallback_path or not (snap_dir / root / fallback_path).exists():
continue
fallback_meta = root_entries.get(fallback_path, {})
outputs.append(
@@ -4112,12 +4112,19 @@ class ArchiveResult(ModelWithDeleteAfter, ModelWithOutputDir, ModelWithNotes):
),
)
- def delete(self, *args, **kwargs):
+ def schedule_delete_cleanup(self, *, using: str | None = None) -> None:
+ """Remove shared plugin output and refresh persisted Snapshot metadata after commit."""
snapshot_id = self.snapshot_id
- deleted = super().delete(*args, **kwargs)
- if snapshot_id:
- transaction.on_commit(lambda: type(self).refresh_snapshot_output_sizes({snapshot_id}))
- return deleted
+ paths = self.validate_output_paths_for_delete(self.output_paths_for_delete())
+
+ def cleanup() -> None:
+ type(self).delete_output_paths(paths)
+ type(self).refresh_snapshot_output_sizes({snapshot_id})
+ snapshot = Snapshot.objects.filter(pk=snapshot_id).first()
+ if snapshot:
+ snapshot.write_index_jsonl()
+
+ transaction.on_commit(cleanup, using=using)
@staticmethod
def refresh_snapshot_output_sizes(snapshot_ids):
diff --git a/archivebox/core/views.py b/archivebox/core/views.py
index dc6557bf..71d47228 100644
--- a/archivebox/core/views.py
+++ b/archivebox/core/views.py
@@ -353,10 +353,15 @@ class SnapshotView(View):
if (out.get("size") or 0) > 0 and out.get("name") not in hidden_card_plugins
]
archiveresults = {}
+ result_ids_by_name = {}
for output in outputs:
+ if output.get("result"):
+ result_ids_by_name.setdefault(output["name"], []).append(str(output["result"].id))
current = archiveresults.get(output["name"])
if current is None or (output.get("size") or 0) > (current.get("size") or 0):
archiveresults[output["name"]] = output
+ for name, output in archiveresults.items():
+ output["result_ids"] = ",".join(result_ids_by_name.get(name, ()))
hash_index = snapshot.hashes_index
loose_items, failed_items = snapshot.get_detail_page_auxiliary_items(
outputs,
@@ -479,6 +484,7 @@ class SnapshotView(View):
"related_years": related_years,
"loose_items": loose_items,
"failed_items": failed_items,
+ "can_delete_outputs": bool(request.user.is_authenticated and request.user.is_active and request.user.is_superuser),
"title_tags": [{"name": tag.name, "style": tag_widget._tag_style(tag.name)} for tag in sorted(tags, key=lambda tag: tag.name)],
}
return render(template_name="core/snapshot.html", request=request, context=context)
diff --git a/archivebox/templates/core/snapshot.html b/archivebox/templates/core/snapshot.html
index cbf6449e..80fdbedf 100644
--- a/archivebox/templates/core/snapshot.html
+++ b/archivebox/templates/core/snapshot.html
@@ -1117,7 +1117,8 @@
line-height: 1;
opacity: 0.8;
}
- .thumb-actions a {
+ .thumb-actions a,
+ .thumb-actions button {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -1127,11 +1128,20 @@
background: #e7ebef;
text-decoration: none;
color: inherit;
+ border: 0;
+ cursor: pointer;
}
- .thumb-actions a:hover {
+ .thumb-actions a:hover,
+ .thumb-actions button:hover {
background: #d9e0e7;
opacity: 1;
}
+ .thumb-actions .delete-pending {
+ width: auto;
+ min-width: 22px;
+ color: #b91c1c;
+ font-weight: 700;
+ }
.thumb-card .thumb-body > a:not(.thumb-actions a),
.thumb-card .thumb-body > h4 {
grid-column: 1;
@@ -1246,11 +1256,15 @@
font-size: 12px;
max-height: 24px;
}
- .thumb-card:has([data-compact]) .thumb-actions a {
+ .thumb-card:has([data-compact]) .thumb-actions a,
+ .thumb-card:has([data-compact]) .thumb-actions button {
width: 18px;
height: 18px;
border-radius: 4px;
}
+ .thumb-card:has([data-compact]) .thumb-actions .delete-pending {
+ width: auto;
+ }
.thumb-card:has([data-compact]) .thumb-body h4 {
font-size: 0.9em;
margin-bottom: 0px;
@@ -1697,6 +1711,9 @@
{% if display_path %}
⬇️
{% endif %}
+ {% if can_delete_outputs and result.result %}
+
+ {% endif %}
{% if display_path %}
@@ -1782,12 +1799,80 @@
+ {% if can_delete_outputs %}{% endif %}