Upload board: dismiss all, real previews for indexed records

- 'dismiss all' clears every indexed record at once
- Indexed cards show the actual file preview: completed records now get a
  signed library media URL (raw for images, thumbnail for videos) so
  <img>/<video> tags can load it, including items hidden from guests
- Media raw/thumbnail endpoints accept the signature for anonymous
  requests and fall back to the normal guest-filtered path otherwise
- Guest blacklist keeps a persistent Redis mirror: an expired TTL or an
  unreachable e621 keeps the last successful list instead of falling
  back to the small local list
This commit is contained in:
2026-09-17 11:29:56 -05:00
parent b71ec729e0
commit 1086beb974
6 changed files with 117 additions and 19 deletions
+26 -2
View File
@@ -3,6 +3,7 @@ from pathlib import Path
from urllib.parse import urlparse
from django.conf import settings
from django.core import signing
from django.db.models import Min
from django.http import Http404
from django.shortcuts import get_object_or_404
@@ -67,9 +68,32 @@ class MediaItemViewSet(
self.check_object_permissions(self.request, obj)
return obj
def _signed_media_item(self, request, action_name):
"""Signed media URLs let <img>/<video> tags bypass guest filtering."""
if request.user.is_authenticated:
return None
signature = request.query_params.get("sig")
if not signature:
return None
try:
payload = signing.loads(
signature, salt=services.MEDIA_FILE_SALT, max_age=86400
)
except signing.BadSignature:
return None
if payload.get("action") != action_name:
return None
return MediaItem.objects.filter(pk=payload.get("item")).first()
def _media_object(self, request, action_name):
item = self._signed_media_item(request, action_name)
if item is not None:
return item
return self.get_object()
@action(detail=True, methods=["get"])
def raw(self, request, pk=None):
item = self.get_object()
item = self._media_object(request, "raw")
location = item.locations.first()
if location is None:
return Response(
@@ -82,7 +106,7 @@ class MediaItemViewSet(
@action(detail=True, methods=["get"])
def thumbnail(self, request, pk=None):
item = self.get_object()
item = self._media_object(request, "thumbnail")
location = item.locations.first()
if location is None:
return Response(