Show IQDB checks in the metadata modal and run them for visual matches
The modal held a snapshot of the staged upload, so IQDB results that landed from the background check queue never appeared until it was closed and reopened — the only hint a check was running was the e621 request history. It now follows the live uploads query, so candidates, progress and errors show up in place. Related gaps fixed along the way: - files flagged by the local visual-similarity check were skipped by the IQDB pass entirely (only 'pending' files were checked), so their modal could only ever show 'already in your library'; unresolved files of both statuses are now checked, and the check button shows on visual-match cards too; - a check with no candidates posted nothing, leaving 'never checked' and 'checked, no match' indistinguishable; results are stored even when empty and the modal now says which one it is; - per-file failures surface in the modal instead of being swallowed, the modal shows a spinner while the query runs and a check now/re-check button, and auto-runs skip files already checked (and videos, since IQDB is image-only). Backend production code unchanged; tests pin the empty-result recording (18 library tests, full suite 56 green).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Staged uploads: complete-set listing and the bulk rating tool."""
|
||||
"""Staged uploads: complete-set listing, bulk rating and IQDB recording."""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
@@ -195,3 +195,88 @@ class BulkResolveTests(TestCase):
|
||||
self.assertEqual(len(body["resolved"]), 1)
|
||||
self.assertEqual(body["errors"][0]["error"], "already in the library")
|
||||
self.assertEqual(MediaItem.objects.count(), 1)
|
||||
|
||||
|
||||
class IqdbRecordingTests(TestCase):
|
||||
"""The modal needs to tell "checked, no match" from "never checked"."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._tmp = tempfile.mkdtemp(prefix="j621-iqdb-")
|
||||
cls._settings = override_settings(MEDIA_ROOT=cls._tmp)
|
||||
cls._settings.enable()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls._settings.disable()
|
||||
shutil.rmtree(cls._tmp, ignore_errors=True)
|
||||
super().tearDownClass()
|
||||
|
||||
def setUp(self):
|
||||
self.uploader = User.objects.create_user(
|
||||
username="iqdb-uploader", password="iqdb-pass-123456"
|
||||
)
|
||||
self.uploader.role = "uploader"
|
||||
self.uploader.save(update_fields=["role"])
|
||||
self.client = Client()
|
||||
self.client.defaults["HTTP_AUTHORIZATION"] = (
|
||||
f"Token {Token.objects.create(user=self.uploader).key}"
|
||||
)
|
||||
|
||||
def make_temp(self):
|
||||
return TempUpload.objects.create(
|
||||
user=self.uploader,
|
||||
file=SimpleUploadedFile(
|
||||
"checked.png", TINY_PNG, content_type="image/png"
|
||||
),
|
||||
original_filename="checked.png",
|
||||
md5=hashlib.md5(b"checked").hexdigest(),
|
||||
size=len(TINY_PNG),
|
||||
)
|
||||
|
||||
def test_empty_result_records_the_check_without_a_match(self):
|
||||
temp = self.make_temp()
|
||||
response = jpost(
|
||||
self.client, f"/api/uploads/{temp.id}/iqdb/", {"results": []}
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
temp.refresh_from_db()
|
||||
self.assertEqual(temp.iqdb_data, [])
|
||||
# No candidates must not masquerade as a visual match.
|
||||
self.assertEqual(temp.status, TempUpload.STATUS_PENDING)
|
||||
|
||||
def test_candidates_are_stored_and_flag_a_visual_match(self):
|
||||
temp = self.make_temp()
|
||||
response = jpost(
|
||||
self.client,
|
||||
f"/api/uploads/{temp.id}/iqdb/",
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"post_id": 123,
|
||||
"score": 91.5,
|
||||
"preview_url": "https://static1.e621.net/data/preview/ab/cd/x.jpg",
|
||||
"rating": "q",
|
||||
"md5": "a" * 32,
|
||||
"score_total": 12,
|
||||
"fav_count": 3,
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"tags_preview": ["canine", "solo"],
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
temp.refresh_from_db()
|
||||
self.assertEqual(len(temp.iqdb_data), 1)
|
||||
self.assertEqual(temp.iqdb_data[0]["post_id"], 123)
|
||||
self.assertEqual(temp.status, TempUpload.STATUS_VISUAL_MATCH)
|
||||
|
||||
def test_rejects_a_non_list_payload(self):
|
||||
temp = self.make_temp()
|
||||
response = jpost(
|
||||
self.client, f"/api/uploads/{temp.id}/iqdb/", {"results": "nope"}
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
Reference in New Issue
Block a user