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:
2026-09-17 18:22:11 -05:00
parent cea34422ec
commit 1c2cb8d468
15 changed files with 875 additions and 5 deletions
+29
View File
@@ -281,3 +281,32 @@ class MatchTask(models.Model):
def __str__(self):
return f"Match scan {self.id} ({self.status})"
class SimilarityCheck(models.Model):
"""An ephemeral image uploaded only to check what it looks like.
Never enters the library: files live in the media temp folder and are
purged on startup, by the TTL and by `manage.py cleanup_similarity`.
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="similarity_checks",
)
file = models.FileField(upload_to="similarity/", blank=True)
original_filename = models.CharField(max_length=255)
md5 = models.CharField(max_length=32, db_index=True, blank=True, default="")
size = models.BigIntegerField(default=0)
results = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
return f"Similarity check {self.id} ({self.original_filename})"