Add an ephemeral similarity check page
- /similar (nav: Similar): drop a file to get the exact MD5 match, the perceptual matches against the library, and e621 IQDB candidates (auto-run for images when credentials are configured). Read-only — nothing enters the library. - SimilarityCheck model + /api/similarity/ (create/list/retrieve/delete) with signed preview URLs and an expires_at timestamp. - Temp files are wiped on startup (AppConfig.ready, file-only so no database access during initialization), lazily past SIMILARITY_TTL_MINUTES (default 30, env-overridable), on delete, and by manage.py cleanup_similarity. - uploadFile() takes a target path; .env.example documents the TTL.
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import signing
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import MediaItem, MediaLocation, TempUpload, DownloadTask, MatchTask
|
||||
from .models import (
|
||||
DownloadTask,
|
||||
MatchTask,
|
||||
MediaItem,
|
||||
MediaLocation,
|
||||
SimilarityCheck,
|
||||
TempUpload,
|
||||
)
|
||||
from .services import (
|
||||
MEDIA_FILE_SALT,
|
||||
UPLOAD_FILE_SALT,
|
||||
@@ -246,3 +255,45 @@ class MatchTaskSerializer(serializers.ModelSerializer):
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class SimilarityCheckSerializer(serializers.ModelSerializer):
|
||||
check_id = serializers.UUIDField(source="id", read_only=True)
|
||||
file_url = serializers.SerializerMethodField()
|
||||
expires_at = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SimilarityCheck
|
||||
fields = [
|
||||
"check_id",
|
||||
"original_filename",
|
||||
"md5",
|
||||
"size",
|
||||
"file_url",
|
||||
"results",
|
||||
"expires_at",
|
||||
"created_at",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def _request_user(self):
|
||||
request = self.context.get("request")
|
||||
user = getattr(request, "user", None)
|
||||
if user is None or not getattr(user, "is_authenticated", False):
|
||||
return None
|
||||
return user
|
||||
|
||||
def get_file_url(self, obj):
|
||||
"""Signed URL so <img> tags can preview the temporary upload."""
|
||||
user = self._request_user()
|
||||
if user is None or not obj.file:
|
||||
return None
|
||||
signature = signing.dumps(
|
||||
{"check": str(obj.id), "user": user.id},
|
||||
salt=UPLOAD_FILE_SALT,
|
||||
)
|
||||
return f"/api/similarity/{obj.id}/file/?sig={signature}"
|
||||
|
||||
def get_expires_at(self, obj):
|
||||
ttl = int(getattr(settings, "SIMILARITY_TTL_MINUTES", 30))
|
||||
return obj.created_at + timedelta(minutes=ttl)
|
||||
|
||||
Reference in New Issue
Block a user