diff --git a/arkindex/documents/fixtures/data.json b/arkindex/documents/fixtures/data.json index bc6f6c63b80c3c3819b562d1a92f3174d813cc4a..0ef2f121170e2db74c72bc3e874e9b6b114c49ae 100644 --- a/arkindex/documents/fixtures/data.json +++ b/arkindex/documents/fixtures/data.json @@ -1,12 +1,12 @@ [ { "model": "process.process", - "pk": "030c7f56-edd8-444e-a97e-c9ca6ba441c3", + "pk": "7b0b9c75-bffc-42f5-a646-c81e8197d171", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "name": null, - "creator": 1, + "creator": 2, "corpus": null, "mode": "local", "revision": null, @@ -36,19 +36,19 @@ }, { "model": "process.process", - "pk": "66a5e619-ec30-4358-8eb7-9e632a271c4d", + "pk": "8cec0605-06e6-4b8f-ba7d-3ec9f9f2a409", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "name": null, + "name": "Process fixture", "creator": 2, - "corpus": null, - "mode": "local", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "mode": "workers", "revision": null, "activity_state": "disabled", "started": null, "finished": null, - "farm": null, + "farm": "409b6859-63b9-41f2-8449-ba737bca6624", "element": null, "folder_type": null, "element_type": null, @@ -71,19 +71,19 @@ }, { "model": "process.process", - "pk": "762dfb6f-17dc-41fc-928c-ce9b03b691a2", + "pk": "9a08ea15-07be-4746-a0d9-7acca68e5c1b", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "name": "Process fixture", - "creator": 2, - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "mode": "workers", + "name": null, + "creator": 1, + "corpus": null, + "mode": "repository", "revision": null, "activity_state": "disabled", "started": null, "finished": null, - "farm": null, + "farm": "409b6859-63b9-41f2-8449-ba737bca6624", "element": null, "folder_type": null, "element_type": null, @@ -106,19 +106,19 @@ }, { "model": "process.process", - "pk": "dd72a1c6-bcb2-4395-963f-0061adf097a5", + "pk": "b4b4b4af-c221-4c47-a784-cc1bac1a99b1", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "name": null, "creator": 1, "corpus": null, - "mode": "repository", + "mode": "local", "revision": null, "activity_state": "disabled", "started": null, "finished": null, - "farm": "293a8d14-2331-44fd-9d73-22364bab9871", + "farm": null, "element": null, "folder_type": null, "element_type": null, @@ -141,176 +141,176 @@ }, { "model": "process.repository", - "pk": "5bf55fb4-d299-4d17-84c9-05bada8def7d", + "pk": "9c6b9576-23e1-4efb-88ce-8a15550b06cb", "fields": { "url": "http://my_repo.fake/workers/worker", "hook_token": "worker-hook-token", - "credentials": "dce89e0f-6b0c-48f3-8fc0-dd6d465c4d1c" + "credentials": "217e2e72-f917-46de-9760-0e140bec08eb" } }, { "model": "process.repository", - "pk": "87c0f610-98c6-421d-b447-e00098ce54af", + "pk": "e4f4a2e6-b23b-48d2-bf86-b60c2b7d2810", "fields": { "url": "http://gitlab/repo", "hook_token": "hook-token", - "credentials": "dce89e0f-6b0c-48f3-8fc0-dd6d465c4d1c" + "credentials": "217e2e72-f917-46de-9760-0e140bec08eb" } }, { "model": "process.revision", - "pk": "983febac-1954-415d-a30f-93ee92651a17", + "pk": "2b024a21-5655-45b7-9fe0-d27b58a6a2d2", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "repo": "87c0f610-98c6-421d-b447-e00098ce54af", - "hash": "42", - "message": "Salve", - "author": "Some user" + "repo": "9c6b9576-23e1-4efb-88ce-8a15550b06cb", + "hash": "1337", + "message": "My w0rk3r", + "author": "Test user" } }, { "model": "process.revision", - "pk": "ed7455b7-96bd-43a1-8af1-7c30cfc2dd31", + "pk": "f7845dce-ce21-49b6-9962-3eb9cec30a6d", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "repo": "5bf55fb4-d299-4d17-84c9-05bada8def7d", - "hash": "1337", - "message": "My w0rk3r", - "author": "Test user" + "repo": "e4f4a2e6-b23b-48d2-bf86-b60c2b7d2810", + "hash": "42", + "message": "Salve", + "author": "Some user" } }, { "model": "process.worker", - "pk": "143c08d5-25f7-4e0d-a262-7c28189d674e", + "pk": "08ef0527-5af6-45c7-a84c-d9d69697e5ca", "fields": { - "name": "File import", - "slug": "file_import", - "type": "19c5eae4-c3cb-4e42-b879-f89c1f911f96", - "repository": "5bf55fb4-d299-4d17-84c9-05bada8def7d", + "name": "Recognizer", + "slug": "reco", + "type": "f089f069-68e8-48cc-a168-19a07a8681f8", + "repository": "9c6b9576-23e1-4efb-88ce-8a15550b06cb", "public": false } }, { "model": "process.worker", - "pk": "4ed33922-00a9-4394-8537-296db5c2a3fb", + "pk": "25bfe0e8-7c7c-491e-b9b4-73498c4b5eb7", "fields": { - "name": "Custom worker", - "slug": "custom", - "type": "eceeafd4-c32f-4735-aa2c-788364f21470", - "repository": null, + "name": "Document layout analyser", + "slug": "dla", + "type": "36873927-4925-4eca-972b-f8dfed39cbe8", + "repository": "9c6b9576-23e1-4efb-88ce-8a15550b06cb", "public": false } }, { "model": "process.worker", - "pk": "c0250ea9-79c5-491e-bc63-236cea4c5946", + "pk": "5222a3e0-b27c-43c2-96df-905613cfd55a", "fields": { - "name": "Recognizer", - "slug": "reco", - "type": "7ec70e61-e745-4e19-834d-582802dac0e9", - "repository": "5bf55fb4-d299-4d17-84c9-05bada8def7d", + "name": "Custom worker", + "slug": "custom", + "type": "5371cc7b-b6c7-407a-a935-e97d05caf1cb", + "repository": null, "public": false } }, { "model": "process.worker", - "pk": "eacb9b80-fd46-4c5d-932d-141abc169c07", + "pk": "913ff861-730c-40e1-9d47-d5d271586c35", "fields": { - "name": "Generic worker with a Model", - "slug": "generic", - "type": "7ec70e61-e745-4e19-834d-582802dac0e9", - "repository": "5bf55fb4-d299-4d17-84c9-05bada8def7d", + "name": "File import", + "slug": "file_import", + "type": "ba1d9a09-48d7-4ac5-8427-bbd51775ea7c", + "repository": "9c6b9576-23e1-4efb-88ce-8a15550b06cb", "public": false } }, { "model": "process.worker", - "pk": "ef01f63e-376e-4fce-b603-cc636e4c702c", + "pk": "ed512f68-389e-4e4d-8f96-103ecab3122b", "fields": { "name": "Worker requiring a GPU", "slug": "worker-gpu", - "type": "9eb34c4d-3ca6-478b-9a6f-f76ca42194c6", - "repository": "5bf55fb4-d299-4d17-84c9-05bada8def7d", + "type": "fd2df8fb-cbc1-4c43-9abe-1a6db6122e59", + "repository": "9c6b9576-23e1-4efb-88ce-8a15550b06cb", "public": false } }, { "model": "process.worker", - "pk": "f247ff6e-4aae-4fd6-93a8-634bcbdc2aec", + "pk": "fc99199f-0f3a-4c0d-a033-db7f151910b1", "fields": { - "name": "Document layout analyser", - "slug": "dla", - "type": "660091d7-7e20-4189-ae55-9771d4b7f43f", - "repository": "5bf55fb4-d299-4d17-84c9-05bada8def7d", + "name": "Generic worker with a Model", + "slug": "generic", + "type": "f089f069-68e8-48cc-a168-19a07a8681f8", + "repository": "9c6b9576-23e1-4efb-88ce-8a15550b06cb", "public": false } }, { "model": "process.workertype", - "pk": "19c5eae4-c3cb-4e42-b879-f89c1f911f96", + "pk": "36873927-4925-4eca-972b-f8dfed39cbe8", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "import", - "display_name": "Import" + "slug": "dla", + "display_name": "Document Layout Analysis" } }, { "model": "process.workertype", - "pk": "660091d7-7e20-4189-ae55-9771d4b7f43f", + "pk": "5371cc7b-b6c7-407a-a935-e97d05caf1cb", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "dla", - "display_name": "Document Layout Analysis" + "slug": "custom", + "display_name": "Custom" } }, { "model": "process.workertype", - "pk": "7ec70e61-e745-4e19-834d-582802dac0e9", + "pk": "ba1d9a09-48d7-4ac5-8427-bbd51775ea7c", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "recognizer", - "display_name": "Recognizer" + "slug": "import", + "display_name": "Import" } }, { "model": "process.workertype", - "pk": "9eb34c4d-3ca6-478b-9a6f-f76ca42194c6", + "pk": "f089f069-68e8-48cc-a168-19a07a8681f8", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "worker", - "display_name": "Worker requiring a GPU" + "slug": "recognizer", + "display_name": "Recognizer" } }, { "model": "process.workertype", - "pk": "eceeafd4-c32f-4735-aa2c-788364f21470", + "pk": "fd2df8fb-cbc1-4c43-9abe-1a6db6122e59", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "slug": "custom", - "display_name": "Custom" + "slug": "worker", + "display_name": "Worker requiring a GPU" } }, { "model": "process.workerversion", - "pk": "140487d3-dbbc-46ae-9802-5dadb105f067", + "pk": "06e97787-7dbd-424b-bd3e-40d4de6b107f", "fields": { - "worker": "4ed33922-00a9-4394-8537-296db5c2a3fb", - "revision": null, - "version": 1, + "worker": "ed512f68-389e-4e4d-8f96-103ecab3122b", + "revision": "2b024a21-5655-45b7-9fe0-d27b58a6a2d2", + "version": null, "configuration": { - "custom": "value" + "test": 42 }, - "state": "created", - "gpu_usage": "disabled", + "state": "available", + "gpu_usage": "required", "model_usage": false, - "docker_image": null, + "docker_image": "a93301c3-bdac-4d06-ba22-b7066decd282", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -318,18 +318,18 @@ }, { "model": "process.workerversion", - "pk": "34af5b14-2d25-4a1e-b62e-1313e0666b0d", + "pk": "5bb8f594-dff8-4145-8634-9ba88ff8a61d", "fields": { - "worker": "f247ff6e-4aae-4fd6-93a8-634bcbdc2aec", - "revision": "ed7455b7-96bd-43a1-8af1-7c30cfc2dd31", - "version": null, + "worker": "5222a3e0-b27c-43c2-96df-905613cfd55a", + "revision": null, + "version": 1, "configuration": { - "test": 42 + "custom": "value" }, - "state": "available", + "state": "created", "gpu_usage": "disabled", "model_usage": false, - "docker_image": "f0677aad-c590-4b9a-a778-de8ea233258f", + "docker_image": null, "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -337,18 +337,16 @@ }, { "model": "process.workerversion", - "pk": "4954d4f6-ad10-4535-92e4-2ed510296e8b", + "pk": "6610938d-c68c-445c-a4cc-d21e574a8958", "fields": { - "worker": "eacb9b80-fd46-4c5d-932d-141abc169c07", - "revision": "ed7455b7-96bd-43a1-8af1-7c30cfc2dd31", + "worker": "913ff861-730c-40e1-9d47-d5d271586c35", + "revision": "2b024a21-5655-45b7-9fe0-d27b58a6a2d2", "version": null, - "configuration": { - "test": 42 - }, + "configuration": {}, "state": "available", "gpu_usage": "disabled", - "model_usage": true, - "docker_image": "f0677aad-c590-4b9a-a778-de8ea233258f", + "model_usage": false, + "docker_image": "a93301c3-bdac-4d06-ba22-b7066decd282", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -356,18 +354,18 @@ }, { "model": "process.workerversion", - "pk": "ba0be3cb-e92a-4e26-b6d5-5b70a29c53c9", + "pk": "daf05df4-45d9-4c60-811a-07bc88cacf0c", "fields": { - "worker": "ef01f63e-376e-4fce-b603-cc636e4c702c", - "revision": "ed7455b7-96bd-43a1-8af1-7c30cfc2dd31", + "worker": "25bfe0e8-7c7c-491e-b9b4-73498c4b5eb7", + "revision": "2b024a21-5655-45b7-9fe0-d27b58a6a2d2", "version": null, "configuration": { "test": 42 }, "state": "available", - "gpu_usage": "required", + "gpu_usage": "disabled", "model_usage": false, - "docker_image": "f0677aad-c590-4b9a-a778-de8ea233258f", + "docker_image": "a93301c3-bdac-4d06-ba22-b7066decd282", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -375,18 +373,18 @@ }, { "model": "process.workerversion", - "pk": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "pk": "e45fe700-2e3d-49c5-a40e-01e46009ac65", "fields": { - "worker": "c0250ea9-79c5-491e-bc63-236cea4c5946", - "revision": "ed7455b7-96bd-43a1-8af1-7c30cfc2dd31", + "worker": "fc99199f-0f3a-4c0d-a033-db7f151910b1", + "revision": "2b024a21-5655-45b7-9fe0-d27b58a6a2d2", "version": null, "configuration": { "test": 42 }, "state": "available", "gpu_usage": "disabled", - "model_usage": false, - "docker_image": "f0677aad-c590-4b9a-a778-de8ea233258f", + "model_usage": true, + "docker_image": "a93301c3-bdac-4d06-ba22-b7066decd282", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -394,16 +392,18 @@ }, { "model": "process.workerversion", - "pk": "c873d2d1-6cea-4be7-808b-6c38f912c3d9", + "pk": "f22fde5b-28db-459a-8257-cab7b58ef63d", "fields": { - "worker": "143c08d5-25f7-4e0d-a262-7c28189d674e", - "revision": "ed7455b7-96bd-43a1-8af1-7c30cfc2dd31", + "worker": "08ef0527-5af6-45c7-a84c-d9d69697e5ca", + "revision": "2b024a21-5655-45b7-9fe0-d27b58a6a2d2", "version": null, - "configuration": {}, + "configuration": { + "test": 42 + }, "state": "available", "gpu_usage": "disabled", "model_usage": false, - "docker_image": "f0677aad-c590-4b9a-a778-de8ea233258f", + "docker_image": "a93301c3-bdac-4d06-ba22-b7066decd282", "docker_image_iid": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" @@ -411,38 +411,38 @@ }, { "model": "process.workerrun", - "pk": "feedd3ff-b318-4b5d-aa39-c9a70568173f", + "pk": "4c989351-7f30-4bb0-8424-31f56b33f925", "fields": { - "process": "762dfb6f-17dc-41fc-928c-ce9b03b691a2", - "version": "34af5b14-2d25-4a1e-b62e-1313e0666b0d", + "process": "7b0b9c75-bffc-42f5-a646-c81e8197d171", + "version": "5bb8f594-dff8-4145-8634-9ba88ff8a61d", "model_version": null, "parents": "[]", "configuration": null, - "summary": "Worker Document layout analyser @ 34af5b", + "summary": "Worker Custom worker @ version 1", "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" } }, { "model": "process.workerrun", - "pk": "1e062420-5a1f-45f7-946b-4bf33dbe9204", + "pk": "5e8879fd-3da3-4bb0-83db-044d97ca9f89", "fields": { - "process": "762dfb6f-17dc-41fc-928c-ce9b03b691a2", - "version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "process": "8cec0605-06e6-4b8f-ba7d-3ec9f9f2a409", + "version": "daf05df4-45d9-4c60-811a-07bc88cacf0c", "model_version": null, - "parents": "[\"feedd3ff-b318-4b5d-aa39-c9a70568173f\"]", + "parents": "[]", "configuration": null, - "summary": "Worker Recognizer @ c75fb7", + "summary": "Worker Document layout analyser @ daf05d", "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" } }, { "model": "process.workerrun", - "pk": "8a0b4acd-caa5-4905-a11e-d6dc917fc2ee", + "pk": "c7fb12ab-d10f-4091-95d2-141e5b249a95", "fields": { - "process": "030c7f56-edd8-444e-a97e-c9ca6ba441c3", - "version": "140487d3-dbbc-46ae-9802-5dadb105f067", + "process": "b4b4b4af-c221-4c47-a784-cc1bac1a99b1", + "version": "5bb8f594-dff8-4145-8634-9ba88ff8a61d", "model_version": null, "parents": "[]", "configuration": null, @@ -453,21 +453,21 @@ }, { "model": "process.workerrun", - "pk": "a0fef862-13a8-4517-8ec9-4f9f1997adb3", + "pk": "2d18ad3f-6e5e-4184-8518-dda1d384164e", "fields": { - "process": "66a5e619-ec30-4358-8eb7-9e632a271c4d", - "version": "140487d3-dbbc-46ae-9802-5dadb105f067", + "process": "8cec0605-06e6-4b8f-ba7d-3ec9f9f2a409", + "version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "model_version": null, - "parents": "[]", + "parents": "[\"5e8879fd-3da3-4bb0-83db-044d97ca9f89\"]", "configuration": null, - "summary": "Worker Custom worker @ version 1", + "summary": "Worker Recognizer @ f22fde", "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z" } }, { "model": "documents.corpus", - "pk": "95305bde-aa8b-476e-85e7-94f974cdb6ad", + "pk": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -480,23 +480,23 @@ }, { "model": "documents.elementtype", - "pk": "026fb2e9-fce8-48a1-8504-eee4f987a56a", + "pk": "16c614aa-d6d4-4f55-afdd-0fada214362a", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "slug": "volume", - "display_name": "Volume", - "folder": true, + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "slug": "surface", + "display_name": "Surface", + "folder": false, "indexable": false, "color": "28b62c" } }, { "model": "documents.elementtype", - "pk": "95341b55-0676-4efc-869f-564516dfc0b5", + "pk": "1acf2b7e-3a52-40b4-b428-40b17992928f", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "slug": "surface", - "display_name": "Surface", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "slug": "act", + "display_name": "Act", "folder": false, "indexable": false, "color": "28b62c" @@ -504,11 +504,11 @@ }, { "model": "documents.elementtype", - "pk": "9f436f44-6680-4e4b-8c23-275876eb923d", + "pk": "5e0a2e15-4493-4d27-a656-29396f01e9f6", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "slug": "page", - "display_name": "Page", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "slug": "word", + "display_name": "Word", "folder": false, "indexable": false, "color": "28b62c" @@ -516,21 +516,21 @@ }, { "model": "documents.elementtype", - "pk": "d0461db2-1867-478e-aa7d-a67215d3c201", + "pk": "83f66e14-eeaf-44e5-8be2-f1eddaa1a549", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "slug": "act", - "display_name": "Act", - "folder": false, + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "slug": "volume", + "display_name": "Volume", + "folder": true, "indexable": false, "color": "28b62c" } }, { "model": "documents.elementtype", - "pk": "ea1b9055-bb71-420f-bc40-fb7b72e550ca", + "pk": "9a893dfa-75b2-4ecd-b8a4-0fa532faadfe", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", "slug": "text_line", "display_name": "Line", "folder": false, @@ -540,11 +540,11 @@ }, { "model": "documents.elementtype", - "pk": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", + "pk": "e2c64e0b-a931-4453-8d9f-e84876583f45", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "slug": "word", - "display_name": "Word", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "slug": "page", + "display_name": "Page", "folder": false, "indexable": false, "color": "28b62c" @@ -552,279 +552,279 @@ }, { "model": "documents.elementpath", - "pk": "05be2d3e-ceea-44c8-b811-3b5fa8961ad7", + "pk": "065e9fcf-4b9e-4ed4-96ab-9a8b4dd7adbd", "fields": { - "element": "81717fcd-dd27-4a80-bf6d-a3cf8c98e541", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"d552e339-359a-4a06-bedf-13ee89919f53\"]", - "ordering": 2 + "element": "4405ebfe-b7db-43d1-8ccc-2f90443631c5", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", + "ordering": 7 } }, { "model": "documents.elementpath", - "pk": "1c27f792-38b9-41ad-bb3f-30ed410e4b96", + "pk": "196df0fb-7dd3-4c21-bec5-86c68f702c2b", "fields": { - "element": "63f2f28e-638b-4c7c-8813-7dcea1436755", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 7 + "element": "d418351a-7ad2-4ee6-9cb4-d306cdd0843d", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"9d4abe00-9e52-4ddf-a89d-5f4a67b6433b\"]", + "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "20f7e2b2-3d68-41e1-bbd8-7ba6a962170c", + "pk": "21c4a14a-77e7-4af1-a521-b1b49b95e190", "fields": { - "element": "20e3b238-0bf5-455b-859b-8d49e4b6d08e", - "path": "[\"38c9c4d0-cba7-4ca5-84cf-157e52296dd5\"]", - "ordering": 1 + "element": "23d22c17-9dc2-4034-978c-5ef2b6eff576", + "path": "[]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "2b95915f-6417-446a-a507-185c3764eb41", + "pk": "23b53148-bd39-4e84-b4f2-ea3f1f601a74", "fields": { - "element": "64cc30db-a652-41ef-a411-2c2691089012", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"e946d43e-7b16-4d90-824f-fc0e1bd17fa2\"]", + "element": "012cd391-bedc-4159-9be0-19d0b3c54f95", + "path": "[]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "2e0fa59d-0dde-4a7f-a608-365fb7335338", + "pk": "37399b33-366c-4c50-bdb1-93286fda8f39", "fields": { - "element": "d552e339-359a-4a06-bedf-13ee89919f53", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 0 + "element": "e8b3cc6e-6518-42d5-928e-d14cb6cd26ac", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"5a2b2988-b759-4fad-8282-1aae9cf39a31\"]", + "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "418c2b3f-d532-4186-8e9a-ceb3f353b777", + "pk": "48721e40-61d5-4860-895d-aea3f41282a3", "fields": { - "element": "eda462b9-61b4-4e77-981f-c003e4aa6d77", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"d552e339-359a-4a06-bedf-13ee89919f53\"]", + "element": "15384754-31b5-43b1-b9e3-b39a07a893d6", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"9d4abe00-9e52-4ddf-a89d-5f4a67b6433b\"]", "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "42de76a9-3ad7-4320-82d0-1d0f3fffc633", + "pk": "65cf5360-7d04-40dd-b74b-a13b70932510", "fields": { - "element": "26e18fb5-9994-41da-b85c-bb22783c160f", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 5 + "element": "393274b6-d4c0-425f-8a4e-6c957d527c83", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"9d4abe00-9e52-4ddf-a89d-5f4a67b6433b\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "4df3f0a6-42b4-49d2-b72f-8063b42e7107", + "pk": "6cbc60e4-a853-4ea8-8f81-001309f1e486", "fields": { - "element": "2c774832-d7ea-46d1-950d-4a711b92346b", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 2 + "element": "69814c5b-6f75-4bcb-aba0-60d9e3093bc5", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"a22f1a4f-ee4e-44f4-8ea8-217f685b77d4\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "51743948-9a7b-40c3-a580-a7597375dfed", + "pk": "7142899c-fe90-4ca5-8ffb-0dcf1d2434a7", "fields": { - "element": "3d180bea-518a-458e-8787-9f454b99758b", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"d552e339-359a-4a06-bedf-13ee89919f53\"]", - "ordering": 3 + "element": "cbbbdf83-7832-4487-a76a-6eabb040df3d", + "path": "[\"23d22c17-9dc2-4034-978c-5ef2b6eff576\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "5c8d8e36-5be9-40e8-bcfd-96c56d2164cf", + "pk": "7bbcace8-10d3-48a5-834f-b34a72b68b8d", "fields": { - "element": "bdc7c183-cf3c-440a-9feb-b9e6ea808106", - "path": "[\"38c9c4d0-cba7-4ca5-84cf-157e52296dd5\"]", + "element": "d7165395-0cbf-4070-bd46-ad050fb63d33", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"b1fef6ad-84b9-4590-82dc-7d5e94f51772\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "5dfb5c73-bee5-4519-857f-682b949c9f4c", + "pk": "7db41281-a9ea-4b19-a599-3147d6e29bdd", "fields": { - "element": "1f52dcb8-b7a0-464f-94db-ac2950cae5b8", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"6fb960a2-8a10-4b4b-a805-cac9fc057d32\"]", - "ordering": 1 + "element": "bd32400c-be2c-4fdf-bbb9-0166aad2e04b", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", + "ordering": 3 } }, { "model": "documents.elementpath", - "pk": "65d33920-14ce-437e-a4cc-5bf8bad71d4b", + "pk": "858bb5c7-4981-4455-bfe6-ea5bbd7405e8", "fields": { - "element": "6fb960a2-8a10-4b4b-a805-cac9fc057d32", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 4 + "element": "b1fef6ad-84b9-4590-82dc-7d5e94f51772", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", + "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "6fff1311-a3e6-4493-a524-9a114aac9b08", + "pk": "878c84b9-6983-481a-8117-7806647c50b0", "fields": { - "element": "5cc12f76-04ce-4d35-b698-57b40e2a4075", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"2c774832-d7ea-46d1-950d-4a711b92346b\"]", - "ordering": 1 + "element": "f18b3232-3ef4-4ae0-a125-02dca0d859d6", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"5a2b2988-b759-4fad-8282-1aae9cf39a31\"]", + "ordering": 3 } }, { "model": "documents.elementpath", - "pk": "7d7d457e-1e60-40c7-a897-9e6564dc6f35", + "pk": "95a27f11-258f-41b8-9f40-dd7ebd26419c", "fields": { - "element": "bbbfa96f-338e-46a0-a3e9-9310f8af25cb", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"e59a3f12-2108-46d3-af15-ff5c48f498cf\"]", - "ordering": 0 + "element": "9e9da196-7011-4c87-be3c-21b580992c93", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", + "ordering": 4 } }, { "model": "documents.elementpath", - "pk": "8c4a4eed-29fc-4ec6-b392-88c3df6fa2ff", + "pk": "99989b98-d1a2-43e6-865c-ecf081147280", "fields": { - "element": "5e609627-d5b7-4812-abcf-544066118037", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"e946d43e-7b16-4d90-824f-fc0e1bd17fa2\"]", + "element": "22a1757a-1af7-4999-8e45-0428ea23cfed", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"b1fef6ad-84b9-4590-82dc-7d5e94f51772\"]", "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "9111cbbd-d4ab-4e4c-b775-95054ec308cb", + "pk": "b1b1288b-90cc-43d6-8f67-8247776093cb", "fields": { - "element": "173a3085-f4c8-43d4-bef1-ae1716fd3ab5", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"6fb960a2-8a10-4b4b-a805-cac9fc057d32\"]", + "element": "deb1ed5d-b954-4450-a4b4-edb0741efec8", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"9e9da196-7011-4c87-be3c-21b580992c93\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "984a36b0-352d-4685-8221-bd15b00d9748", + "pk": "b96f33dc-316f-43b3-8a4d-2d42232f470d", "fields": { - "element": "3cb412b2-2fce-4dac-b080-49b22bcdb024", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"26e18fb5-9994-41da-b85c-bb22783c160f\"]", - "ordering": 0 + "element": "ad6483f2-c90a-41ba-9dbd-9cd3dbf78146", + "path": "[\"23d22c17-9dc2-4034-978c-5ef2b6eff576\"]", + "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "a33e0fe6-7840-40da-bce3-ddc701c81e5d", + "pk": "bce997cc-26c1-4175-9ff8-5fc36ab6f32d", "fields": { - "element": "38c9c4d0-cba7-4ca5-84cf-157e52296dd5", - "path": "[]", - "ordering": 0 + "element": "d6673022-bbc1-49a6-9d15-3eb68c7f7616", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", + "ordering": 6 } }, { "model": "documents.elementpath", - "pk": "a699fcb2-0193-4834-8198-26d9bf438a3c", + "pk": "c3ee3805-4418-4fdb-885b-19f3e8044ca4", "fields": { - "element": "17d82793-7857-492d-8a02-801d3c177abc", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 6 + "element": "05ca5b27-06c8-4337-8548-9de459d39024", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"b1fef6ad-84b9-4590-82dc-7d5e94f51772\"]", + "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "c08e2df5-3134-4191-bf1c-d3fc48a1e6bf", + "pk": "c7d8d602-d24b-4d57-a370-3eed48992776", "fields": { - "element": "36e0f813-43ae-4a38-b719-f9bfafbd6892", - "path": "[]", + "element": "5beef449-d994-407d-a881-d4a7d0e14ad7", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"5a2b2988-b759-4fad-8282-1aae9cf39a31\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "c50d93e3-b516-4024-91e6-8f3b393553fb", + "pk": "cbc0702b-4cc2-4b85-a310-1a288bbec5ba", "fields": { - "element": "b1fb7a42-85e6-4310-854f-c308edd4e1af", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"d552e339-359a-4a06-bedf-13ee89919f53\"]", + "element": "bd078be2-bff4-4c8b-ae78-241f1e4b144b", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"4405ebfe-b7db-43d1-8ccc-2f90443631c5\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "caf2f5ea-b9c6-4c80-b0a9-5a082f111469", + "pk": "dac53794-ecac-486b-b9eb-60788dbf6a66", "fields": { - "element": "e946d43e-7b16-4d90-824f-fc0e1bd17fa2", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 1 + "element": "5a2b2988-b759-4fad-8282-1aae9cf39a31", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "cb8fabfc-9d74-45a1-b5c5-bf28ffb593be", + "pk": "db6fbd1f-d5ce-4b16-bc6b-3c96ae7234ca", "fields": { - "element": "e59a3f12-2108-46d3-af15-ff5c48f498cf", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\"]", - "ordering": 3 + "element": "1a5805d2-da50-4fc3-916f-92402e479e6e", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"5a2b2988-b759-4fad-8282-1aae9cf39a31\"]", + "ordering": 2 } }, { "model": "documents.elementpath", - "pk": "d42bee6f-d53a-49fa-8db4-e01d2ebaa635", + "pk": "ddcb97c4-a28f-4936-938f-99d6fd5be54e", "fields": { - "element": "c777ef49-2f0a-4762-b89d-1c3986d26ddd", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"63f2f28e-638b-4c7c-8813-7dcea1436755\"]", - "ordering": 0 + "element": "a22f1a4f-ee4e-44f4-8ea8-217f685b77d4", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", + "ordering": 5 } }, { "model": "documents.elementpath", - "pk": "d8cebde2-5351-4655-ba74-d3dfd31000ea", + "pk": "e40b2625-b378-4a15-96aa-401a6970f5e8", "fields": { - "element": "1da1b0d6-9e20-49e7-a2b3-e183499831cf", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"2c774832-d7ea-46d1-950d-4a711b92346b\"]", + "element": "96f1d3f6-c65a-44bd-827f-d2569aecc629", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"bd32400c-be2c-4fdf-bbb9-0166aad2e04b\"]", "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "d9a2e48b-8f67-4cda-9f9b-5d2f3cf6f8bc", + "pk": "e87ca8aa-d0f3-4beb-8af6-438b4f3d543a", "fields": { - "element": "98ef0d90-b66c-48d6-89e1-3f5a29c9a207", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"e946d43e-7b16-4d90-824f-fc0e1bd17fa2\"]", - "ordering": 2 + "element": "29f17e00-1d4a-46d8-91c3-db65667c2503", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"d6673022-bbc1-49a6-9d15-3eb68c7f7616\"]", + "ordering": 0 } }, { "model": "documents.elementpath", - "pk": "e44c2505-06a7-4c27-a383-e3ba6151448a", + "pk": "eba4b8e5-37fa-4d92-9136-fad469f1cc64", "fields": { - "element": "579c17dd-db34-47a5-9f5a-99ed663b115c", - "path": "[\"38c9c4d0-cba7-4ca5-84cf-157e52296dd5\"]", - "ordering": 2 + "element": "28eb7ab5-b1da-46d2-bb94-f461570a4918", + "path": "[\"23d22c17-9dc2-4034-978c-5ef2b6eff576\"]", + "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "f51c5a8a-7db3-49a8-8c60-805c8fa4653f", + "pk": "f1bb6429-7ddb-4bf5-8dde-fa7b4451ae7b", "fields": { - "element": "d44ea46f-7caa-4dfc-8fee-974f5f214e39", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"17d82793-7857-492d-8a02-801d3c177abc\"]", - "ordering": 0 + "element": "80bbf4bf-5840-400d-a450-b777cd7508aa", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\", \"9e9da196-7011-4c87-be3c-21b580992c93\"]", + "ordering": 1 } }, { "model": "documents.elementpath", - "pk": "f6db2ca2-b83b-49d1-8248-94bf3e67682b", + "pk": "f40e4025-286a-4758-93b9-660672e58f85", "fields": { - "element": "a94434c1-f543-463b-9871-f8fd3ccb0c30", - "path": "[\"36e0f813-43ae-4a38-b719-f9bfafbd6892\", \"2c774832-d7ea-46d1-950d-4a711b92346b\"]", + "element": "9d4abe00-9e52-4ddf-a89d-5f4a67b6433b", + "path": "[\"012cd391-bedc-4159-9be0-19d0b3c54f95\"]", "ordering": 2 } }, { "model": "documents.element", - "pk": "173a3085-f4c8-43d4-bef1-ae1716fd3ab5", + "pk": "012cd391-bedc-4159-9be0-19d0b3c54f95", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "95341b55-0676-4efc-869f-564516dfc0b5", - "name": "Surface B", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "83f66e14-eeaf-44e5-8be2-f1eddaa1a549", + "name": "Volume 1", "creator": null, "worker_version": null, "worker_run": null, - "image": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", - "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -832,18 +832,18 @@ }, { "model": "documents.element", - "pk": "17d82793-7857-492d-8a02-801d3c177abc", + "pk": "05ca5b27-06c8-4337-8548-9de459d39024", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "d0461db2-1867-478e-aa7d-a67215d3c201", - "name": "Act 4", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "DATUM", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "749d041a-c431-4617-85ff-a0628f38737d", + "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -851,18 +851,18 @@ }, { "model": "documents.element", - "pk": "1da1b0d6-9e20-49e7-a2b3-e183499831cf", + "pk": "15384754-31b5-43b1-b9e3-b39a07a893d6", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "PARIS", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "ROY", "creator": null, "worker_version": null, "worker_run": null, - "image": "45cfd7e6-763e-4429-b92c-092ea0eb252b", - "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", + "image": "9f324936-0561-432a-8985-103b240e55ed", + "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -870,18 +870,18 @@ }, { "model": "documents.element", - "pk": "1f52dcb8-b7a0-464f-94db-ac2950cae5b8", + "pk": "1a5805d2-da50-4fc3-916f-92402e479e6e", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "95341b55-0676-4efc-869f-564516dfc0b5", - "name": "Surface C", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "DATUM", "creator": null, "worker_version": null, "worker_run": null, - "image": "a47f3bc7-d6e0-4810-beec-6f57bc61b186", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "71b96a29-ad88-4906-abeb-fd4a50d61af1", + "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -889,18 +889,18 @@ }, { "model": "documents.element", - "pk": "20e3b238-0bf5-455b-859b-8d49e4b6d08e", + "pk": "22a1757a-1af7-4999-8e45-0428ea23cfed", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "9f436f44-6680-4e4b-8c23-275876eb923d", - "name": "Volume 2, page 1v", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "ROY", "creator": null, "worker_version": null, "worker_run": null, - "image": "b4cca2c4-04dc-4c7c-a2b6-fba4bc95ce45", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "749d041a-c431-4617-85ff-a0628f38737d", + "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -908,13 +908,13 @@ }, { "model": "documents.element", - "pk": "26e18fb5-9994-41da-b85c-bb22783c160f", + "pk": "23d22c17-9dc2-4034-978c-5ef2b6eff576", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "d0461db2-1867-478e-aa7d-a67215d3c201", - "name": "Act 3", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "83f66e14-eeaf-44e5-8be2-f1eddaa1a549", + "name": "Volume 2", "creator": null, "worker_version": null, "worker_run": null, @@ -927,17 +927,17 @@ }, { "model": "documents.element", - "pk": "2c774832-d7ea-46d1-950d-4a711b92346b", + "pk": "28eb7ab5-b1da-46d2-bb94-f461570a4918", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "9f436f44-6680-4e4b-8c23-275876eb923d", - "name": "Volume 1, page 2r", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "e2c64e0b-a931-4453-8d9f-e84876583f45", + "name": "Volume 2, page 1v", "creator": null, "worker_version": null, "worker_run": null, - "image": "45cfd7e6-763e-4429-b92c-092ea0eb252b", + "image": "3ce03720-068d-45f0-9237-df2d744ef953", "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, @@ -946,18 +946,18 @@ }, { "model": "documents.element", - "pk": "36e0f813-43ae-4a38-b719-f9bfafbd6892", + "pk": "29f17e00-1d4a-46d8-91c3-db65667c2503", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "026fb2e9-fce8-48a1-8504-eee4f987a56a", - "name": "Volume 1", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "16c614aa-d6d4-4f55-afdd-0fada214362a", + "name": "Surface E", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "9f324936-0561-432a-8985-103b240e55ed", + "polygon": "LINEARRING (300 300, 300 600, 600 600, 600 300, 300 300)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -965,18 +965,18 @@ }, { "model": "documents.element", - "pk": "38c9c4d0-cba7-4ca5-84cf-157e52296dd5", + "pk": "393274b6-d4c0-425f-8a4e-6c957d527c83", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "026fb2e9-fce8-48a1-8504-eee4f987a56a", - "name": "Volume 2", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "PARIS", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "9f324936-0561-432a-8985-103b240e55ed", + "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -984,18 +984,18 @@ }, { "model": "documents.element", - "pk": "3cb412b2-2fce-4dac-b080-49b22bcdb024", + "pk": "4405ebfe-b7db-43d1-8ccc-2f90443631c5", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "95341b55-0676-4efc-869f-564516dfc0b5", - "name": "Surface D", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "1acf2b7e-3a52-40b4-b428-40b17992928f", + "name": "Act 5", "creator": null, "worker_version": null, "worker_run": null, - "image": "45cfd7e6-763e-4429-b92c-092ea0eb252b", - "polygon": "LINEARRING (0 0, 0 300, 300 300, 300 0, 0 0)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1003,18 +1003,18 @@ }, { "model": "documents.element", - "pk": "3d180bea-518a-458e-8787-9f454b99758b", + "pk": "5a2b2988-b759-4fad-8282-1aae9cf39a31", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ea1b9055-bb71-420f-bc40-fb7b72e550ca", - "name": "Text line", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "e2c64e0b-a931-4453-8d9f-e84876583f45", + "name": "Volume 1, page 1r", "creator": null, "worker_version": null, "worker_run": null, - "image": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", - "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", + "image": "71b96a29-ad88-4906-abeb-fd4a50d61af1", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1022,18 +1022,18 @@ }, { "model": "documents.element", - "pk": "579c17dd-db34-47a5-9f5a-99ed663b115c", + "pk": "5beef449-d994-407d-a881-d4a7d0e14ad7", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "9f436f44-6680-4e4b-8c23-275876eb923d", - "name": "Volume 2, page 2r", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "PARIS", "creator": null, "worker_version": null, "worker_run": null, - "image": "17e5e746-3307-4bf2-a359-663e12bcbc17", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "71b96a29-ad88-4906-abeb-fd4a50d61af1", + "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1041,18 +1041,18 @@ }, { "model": "documents.element", - "pk": "5cc12f76-04ce-4d35-b698-57b40e2a4075", + "pk": "69814c5b-6f75-4bcb-aba0-60d9e3093bc5", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "ROY", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "16c614aa-d6d4-4f55-afdd-0fada214362a", + "name": "Surface D", "creator": null, "worker_version": null, "worker_run": null, - "image": "45cfd7e6-763e-4429-b92c-092ea0eb252b", - "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", + "image": "9f324936-0561-432a-8985-103b240e55ed", + "polygon": "LINEARRING (0 0, 0 300, 300 300, 300 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1060,18 +1060,18 @@ }, { "model": "documents.element", - "pk": "5e609627-d5b7-4812-abcf-544066118037", + "pk": "80bbf4bf-5840-400d-a450-b777cd7508aa", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "ROY", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "16c614aa-d6d4-4f55-afdd-0fada214362a", + "name": "Surface C", "creator": null, "worker_version": null, "worker_run": null, - "image": "a47f3bc7-d6e0-4810-beec-6f57bc61b186", - "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", + "image": "749d041a-c431-4617-85ff-a0628f38737d", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1079,18 +1079,18 @@ }, { "model": "documents.element", - "pk": "63f2f28e-638b-4c7c-8813-7dcea1436755", + "pk": "96f1d3f6-c65a-44bd-827f-d2569aecc629", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "d0461db2-1867-478e-aa7d-a67215d3c201", - "name": "Act 5", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "16c614aa-d6d4-4f55-afdd-0fada214362a", + "name": "Surface A", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "71b96a29-ad88-4906-abeb-fd4a50d61af1", + "polygon": "LINEARRING (0 0, 0 600, 600 600, 600 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1098,18 +1098,18 @@ }, { "model": "documents.element", - "pk": "64cc30db-a652-41ef-a411-2c2691089012", + "pk": "9d4abe00-9e52-4ddf-a89d-5f4a67b6433b", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "PARIS", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "e2c64e0b-a931-4453-8d9f-e84876583f45", + "name": "Volume 1, page 2r", "creator": null, "worker_version": null, "worker_run": null, - "image": "a47f3bc7-d6e0-4810-beec-6f57bc61b186", - "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", + "image": "9f324936-0561-432a-8985-103b240e55ed", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1117,12 +1117,12 @@ }, { "model": "documents.element", - "pk": "6fb960a2-8a10-4b4b-a805-cac9fc057d32", + "pk": "9e9da196-7011-4c87-be3c-21b580992c93", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "d0461db2-1867-478e-aa7d-a67215d3c201", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "1acf2b7e-3a52-40b4-b428-40b17992928f", "name": "Act 2", "creator": null, "worker_version": null, @@ -1136,18 +1136,18 @@ }, { "model": "documents.element", - "pk": "81717fcd-dd27-4a80-bf6d-a3cf8c98e541", + "pk": "a22f1a4f-ee4e-44f4-8ea8-217f685b77d4", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "DATUM", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "1acf2b7e-3a52-40b4-b428-40b17992928f", + "name": "Act 3", "creator": null, "worker_version": null, "worker_run": null, - "image": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", - "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1155,18 +1155,18 @@ }, { "model": "documents.element", - "pk": "98ef0d90-b66c-48d6-89e1-3f5a29c9a207", + "pk": "ad6483f2-c90a-41ba-9dbd-9cd3dbf78146", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "DATUM", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "e2c64e0b-a931-4453-8d9f-e84876583f45", + "name": "Volume 2, page 2r", "creator": null, "worker_version": null, "worker_run": null, - "image": "a47f3bc7-d6e0-4810-beec-6f57bc61b186", - "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", + "image": "7841d614-be5e-4ff6-82dd-4089cc644a19", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1174,18 +1174,18 @@ }, { "model": "documents.element", - "pk": "a94434c1-f543-463b-9871-f8fd3ccb0c30", + "pk": "b1fef6ad-84b9-4590-82dc-7d5e94f51772", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "DATUM", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "e2c64e0b-a931-4453-8d9f-e84876583f45", + "name": "Volume 1, page 1v", "creator": null, "worker_version": null, "worker_run": null, - "image": "45cfd7e6-763e-4429-b92c-092ea0eb252b", - "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", + "image": "749d041a-c431-4617-85ff-a0628f38737d", + "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1193,18 +1193,18 @@ }, { "model": "documents.element", - "pk": "b1fb7a42-85e6-4310-854f-c308edd4e1af", + "pk": "bd078be2-bff4-4c8b-ae78-241f1e4b144b", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "PARIS", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "16c614aa-d6d4-4f55-afdd-0fada214362a", + "name": "Surface F", "creator": null, "worker_version": null, "worker_run": null, - "image": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", - "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", + "image": "9f324936-0561-432a-8985-103b240e55ed", + "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1212,18 +1212,18 @@ }, { "model": "documents.element", - "pk": "bbbfa96f-338e-46a0-a3e9-9310f8af25cb", + "pk": "bd32400c-be2c-4fdf-bbb9-0166aad2e04b", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "95341b55-0676-4efc-869f-564516dfc0b5", - "name": "Surface A", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "1acf2b7e-3a52-40b4-b428-40b17992928f", + "name": "Act 1", "creator": null, "worker_version": null, "worker_run": null, - "image": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", - "polygon": "LINEARRING (0 0, 0 600, 600 600, 600 0, 0 0)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1231,17 +1231,17 @@ }, { "model": "documents.element", - "pk": "bdc7c183-cf3c-440a-9feb-b9e6ea808106", + "pk": "cbbbdf83-7832-4487-a76a-6eabb040df3d", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "9f436f44-6680-4e4b-8c23-275876eb923d", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "e2c64e0b-a931-4453-8d9f-e84876583f45", "name": "Volume 2, page 1r", "creator": null, "worker_version": null, "worker_run": null, - "image": "bb20838d-6fa5-4e64-93fc-c76d4248934e", + "image": "2ee6f6af-ee3e-4ea8-86b6-d4bb0e4514ee", "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", "rotation_angle": 0, "mirrored": false, @@ -1250,18 +1250,18 @@ }, { "model": "documents.element", - "pk": "c777ef49-2f0a-4762-b89d-1c3986d26ddd", + "pk": "d418351a-7ad2-4ee6-9cb4-d306cdd0843d", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "95341b55-0676-4efc-869f-564516dfc0b5", - "name": "Surface F", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "DATUM", "creator": null, "worker_version": null, "worker_run": null, - "image": "45cfd7e6-763e-4429-b92c-092ea0eb252b", - "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", + "image": "9f324936-0561-432a-8985-103b240e55ed", + "polygon": "LINEARRING (700 700, 700 800, 800 800, 800 700, 700 700)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1269,18 +1269,18 @@ }, { "model": "documents.element", - "pk": "d44ea46f-7caa-4dfc-8fee-974f5f214e39", + "pk": "d6673022-bbc1-49a6-9d15-3eb68c7f7616", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "95341b55-0676-4efc-869f-564516dfc0b5", - "name": "Surface E", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "1acf2b7e-3a52-40b4-b428-40b17992928f", + "name": "Act 4", "creator": null, "worker_version": null, "worker_run": null, - "image": "45cfd7e6-763e-4429-b92c-092ea0eb252b", - "polygon": "LINEARRING (300 300, 300 600, 600 600, 600 300, 300 300)", + "image": null, + "polygon": null, "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1288,18 +1288,18 @@ }, { "model": "documents.element", - "pk": "d552e339-359a-4a06-bedf-13ee89919f53", + "pk": "d7165395-0cbf-4070-bd46-ad050fb63d33", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "9f436f44-6680-4e4b-8c23-275876eb923d", - "name": "Volume 1, page 1r", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "PARIS", "creator": null, "worker_version": null, "worker_run": null, - "image": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "749d041a-c431-4617-85ff-a0628f38737d", + "polygon": "LINEARRING (100 100, 100 200, 200 200, 200 100, 100 100)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1307,18 +1307,18 @@ }, { "model": "documents.element", - "pk": "e59a3f12-2108-46d3-af15-ff5c48f498cf", + "pk": "deb1ed5d-b954-4450-a4b4-edb0741efec8", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "d0461db2-1867-478e-aa7d-a67215d3c201", - "name": "Act 1", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "16c614aa-d6d4-4f55-afdd-0fada214362a", + "name": "Surface B", "creator": null, "worker_version": null, "worker_run": null, - "image": null, - "polygon": null, + "image": "71b96a29-ad88-4906-abeb-fd4a50d61af1", + "polygon": "LINEARRING (600 600, 600 1000, 1000 1000, 1000 600, 600 600)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1326,18 +1326,18 @@ }, { "model": "documents.element", - "pk": "e946d43e-7b16-4d90-824f-fc0e1bd17fa2", + "pk": "e8b3cc6e-6518-42d5-928e-d14cb6cd26ac", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "9f436f44-6680-4e4b-8c23-275876eb923d", - "name": "Volume 1, page 1v", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "5e0a2e15-4493-4d27-a656-29396f01e9f6", + "name": "ROY", "creator": null, "worker_version": null, "worker_run": null, - "image": "a47f3bc7-d6e0-4810-beec-6f57bc61b186", - "polygon": "LINEARRING (0 0, 0 1000, 1000 1000, 1000 0, 0 0)", + "image": "71b96a29-ad88-4906-abeb-fd4a50d61af1", + "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, "confidence": null @@ -1345,17 +1345,17 @@ }, { "model": "documents.element", - "pk": "eda462b9-61b4-4e77-981f-c003e4aa6d77", + "pk": "f18b3232-3ef4-4ae0-a125-02dca0d859d6", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "ff7c4111-4fa3-4e15-b2e2-7744ffed7cbb", - "name": "ROY", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "9a893dfa-75b2-4ecd-b8a4-0fa532faadfe", + "name": "Text line", "creator": null, "worker_version": null, "worker_run": null, - "image": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", + "image": "71b96a29-ad88-4906-abeb-fd4a50d61af1", "polygon": "LINEARRING (400 400, 400 500, 500 500, 500 400, 400 400)", "rotation_angle": 0, "mirrored": false, @@ -1364,103 +1364,103 @@ }, { "model": "documents.entitytype", - "pk": "082745b8-ac67-4e1f-922c-1e1b1e41fcf1", + "pk": "311d11f1-9a0d-42e8-b261-7f921d325a22", "fields": { - "name": "person", + "name": "location", "color": "ff0000", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad" + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68" } }, { "model": "documents.entitytype", - "pk": "0850ad59-c5a5-413d-b684-c7754e8402cb", + "pk": "7e7f01f6-fea3-454a-870f-1ef8a4416512", "fields": { "name": "date", "color": "ff0000", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad" + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68" } }, { "model": "documents.entitytype", - "pk": "221f4ce9-93bb-476e-bfc2-edf9027a0472", + "pk": "bbaaa893-ad44-45a0-aa03-975c8787ade1", "fields": { - "name": "organization", + "name": "number", "color": "ff0000", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad" + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68" } }, { "model": "documents.entitytype", - "pk": "b9e985dd-a5d7-466d-b9dd-f88ec95dcc3f", + "pk": "c0537bd2-7cc8-4612-abba-839869668a8c", "fields": { - "name": "location", + "name": "organization", "color": "ff0000", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad" + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68" } }, { "model": "documents.entitytype", - "pk": "f71aa000-9ba8-4ee0-b7cb-18eecfee6f12", + "pk": "d352ac6e-68a2-415b-898f-e234e58fb9f4", "fields": { - "name": "number", + "name": "person", "color": "ff0000", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad" + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68" } }, { "model": "documents.transcription", - "pk": "015bc870-a1c2-4497-b631-8be27269eaf4", + "pk": "081a5c4f-7af4-4705-aa97-aeff028176dd", "fields": { - "element": "81717fcd-dd27-4a80-bf6d-a3cf8c98e541", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "5a2b2988-b759-4fad-8282-1aae9cf39a31", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, - "text": "DATUM", + "text": "Lorem ipsum dolor sit amet", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "3e41bcc8-365f-4757-8ac2-55c1e53c6c5c", + "pk": "162d8843-380a-4e28-9132-642fa8639018", "fields": { - "element": "5cc12f76-04ce-4d35-b698-57b40e2a4075", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "393274b6-d4c0-425f-8a4e-6c957d527c83", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, - "text": "ROY", + "text": "PARIS", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "45e980df-c8e5-4fd3-97b9-d623278439a6", + "pk": "23641b79-24ef-4931-8cb4-57dc8c3d71d8", "fields": { - "element": "1da1b0d6-9e20-49e7-a2b3-e183499831cf", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "e8b3cc6e-6518-42d5-928e-d14cb6cd26ac", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, - "text": "PARIS", + "text": "ROY", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "7c449bc8-3183-44cc-9585-953243337b1c", + "pk": "39d8309a-5ee5-4654-be82-c863c4c3719c", "fields": { - "element": "eda462b9-61b4-4e77-981f-c003e4aa6d77", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "05ca5b27-06c8-4337-8548-9de459d39024", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, - "text": "ROY", + "text": "DATUM", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "a4a9a230-69bb-4464-ba6c-c430d3d551f9", + "pk": "4bb4012c-53b2-41be-ac0c-9d7b5067dc25", "fields": { - "element": "5e609627-d5b7-4812-abcf-544066118037", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "15384754-31b5-43b1-b9e3-b39a07a893d6", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, "text": "ROY", "orientation": "horizontal-lr", @@ -1469,22 +1469,22 @@ }, { "model": "documents.transcription", - "pk": "a77f50f5-d22e-408c-8cdc-b6017a64c566", + "pk": "62bd07d6-3521-408c-b01c-e4005677b906", "fields": { - "element": "b1fb7a42-85e6-4310-854f-c308edd4e1af", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "1a5805d2-da50-4fc3-916f-92402e479e6e", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, - "text": "PARIS", + "text": "DATUM", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "c8214fc7-5772-4663-a62b-9917a745231f", + "pk": "764094d1-cef3-40d5-bdb9-849f802d9335", "fields": { - "element": "64cc30db-a652-41ef-a411-2c2691089012", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "5beef449-d994-407d-a881-d4a7d0e14ad7", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, "text": "PARIS", "orientation": "horizontal-lr", @@ -1493,22 +1493,22 @@ }, { "model": "documents.transcription", - "pk": "da9e48aa-5bed-476e-88fd-4aa0abc9110f", + "pk": "aef2094a-b509-4865-bbad-698ab78984ec", "fields": { - "element": "a94434c1-f543-463b-9871-f8fd3ccb0c30", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "22a1757a-1af7-4999-8e45-0428ea23cfed", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, - "text": "DATUM", + "text": "ROY", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.transcription", - "pk": "dc4d1253-f2c2-43a2-9e22-822d2cab621c", + "pk": "c9bb2b39-7f18-4979-afb7-c544ee8e6f4b", "fields": { - "element": "98ef0d90-b66c-48d6-89e1-3f5a29c9a207", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "d418351a-7ad2-4ee6-9cb4-d306cdd0843d", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, "text": "DATUM", "orientation": "horizontal-lr", @@ -1517,51 +1517,51 @@ }, { "model": "documents.transcription", - "pk": "f1b6e2ae-ef3a-4b87-a0cd-ddaad00fea03", + "pk": "dc25ce06-ac14-44c6-8ad6-aa1d3ab20551", "fields": { - "element": "d552e339-359a-4a06-bedf-13ee89919f53", - "worker_version": "c75fb73c-53c6-426a-be95-b193d8f663f3", + "element": "d7165395-0cbf-4070-bd46-ad050fb63d33", + "worker_version": "f22fde5b-28db-459a-8257-cab7b58ef63d", "worker_run": null, - "text": "Lorem ipsum dolor sit amet", + "text": "PARIS", "orientation": "horizontal-lr", "confidence": 1.0 } }, { "model": "documents.allowedmetadata", - "pk": "1d924a85-67a2-40ea-9c54-323ee017d5fb", + "pk": "3b1250cb-94e7-4f8f-90f8-469ad21e2bf8", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", "type": "date", "name": "date" } }, { "model": "documents.allowedmetadata", - "pk": "cb202a56-ac68-4190-9e1b-2a2918b46e82", + "pk": "4f16502f-3813-43e0-871f-0984a1082672", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "text", - "name": "folio" + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "location", + "name": "location" } }, { "model": "documents.allowedmetadata", - "pk": "d88efccd-04be-41a5-87b3-2d500697af33", + "pk": "b965e5fc-e3ba-4232-a710-57b2c2144dda", "fields": { - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", - "type": "location", - "name": "location" + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", + "type": "text", + "name": "folio" } }, { "model": "documents.metadata", - "pk": "0fc10f4e-9d95-47ea-b33b-20d0ca576972", + "pk": "16f42e57-9495-406c-baae-51be7c97c544", "fields": { - "element": "63f2f28e-638b-4c7c-8813-7dcea1436755", - "name": "number", + "element": "9d4abe00-9e52-4ddf-a89d-5f4a67b6433b", + "name": "folio", "type": "text", - "value": "5", + "value": "2r", "entity": null, "worker_version": null, "worker_run": null @@ -1569,12 +1569,12 @@ }, { "model": "documents.metadata", - "pk": "446824a7-e898-44b1-95a7-dabe9ac60386", + "pk": "43f60c5e-2fbd-4ac5-ae01-5aa73ebd4e27", "fields": { - "element": "20e3b238-0bf5-455b-859b-8d49e4b6d08e", - "name": "folio", + "element": "9e9da196-7011-4c87-be3c-21b580992c93", + "name": "number", "type": "text", - "value": "1v", + "value": "2", "entity": null, "worker_version": null, "worker_run": null @@ -1582,12 +1582,12 @@ }, { "model": "documents.metadata", - "pk": "46c5eda1-20f8-4d51-b3ef-e29ed8d96bbc", + "pk": "49afc57d-3852-439d-aec3-dc9bae75d317", "fields": { - "element": "579c17dd-db34-47a5-9f5a-99ed663b115c", + "element": "28eb7ab5-b1da-46d2-bb94-f461570a4918", "name": "folio", "type": "text", - "value": "2r", + "value": "1v", "entity": null, "worker_version": null, "worker_run": null @@ -1595,12 +1595,12 @@ }, { "model": "documents.metadata", - "pk": "4e22c716-e6ba-42cd-8b10-75d82844f7d0", + "pk": "7172ad8f-274e-4762-aa71-4b67e831a085", "fields": { - "element": "17d82793-7857-492d-8a02-801d3c177abc", - "name": "number", + "element": "cbbbdf83-7832-4487-a76a-6eabb040df3d", + "name": "folio", "type": "text", - "value": "4", + "value": "1r", "entity": null, "worker_version": null, "worker_run": null @@ -1608,12 +1608,12 @@ }, { "model": "documents.metadata", - "pk": "514c298e-f2d8-4222-9b42-410d0af27fdd", + "pk": "7feebc85-ae0c-4f84-a9c8-719c0334f365", "fields": { - "element": "26e18fb5-9994-41da-b85c-bb22783c160f", - "name": "number", + "element": "5a2b2988-b759-4fad-8282-1aae9cf39a31", + "name": "folio", "type": "text", - "value": "3", + "value": "1r", "entity": null, "worker_version": null, "worker_run": null @@ -1621,12 +1621,12 @@ }, { "model": "documents.metadata", - "pk": "936ffaa4-5b0d-440c-a27e-326171be7b7b", + "pk": "995ffc3d-fd2b-4aa1-a0fd-bb06818d13c4", "fields": { - "element": "e946d43e-7b16-4d90-824f-fc0e1bd17fa2", - "name": "folio", + "element": "d6673022-bbc1-49a6-9d15-3eb68c7f7616", + "name": "number", "type": "text", - "value": "1v", + "value": "4", "entity": null, "worker_version": null, "worker_run": null @@ -1634,12 +1634,12 @@ }, { "model": "documents.metadata", - "pk": "9f9d61ba-c0b0-4d1a-985a-5d8cc5b6ee90", + "pk": "9a20fe23-faf6-4579-8aa3-164bf0ebc9db", "fields": { - "element": "2c774832-d7ea-46d1-950d-4a711b92346b", - "name": "folio", + "element": "4405ebfe-b7db-43d1-8ccc-2f90443631c5", + "name": "number", "type": "text", - "value": "2r", + "value": "5", "entity": null, "worker_version": null, "worker_run": null @@ -1647,9 +1647,9 @@ }, { "model": "documents.metadata", - "pk": "a8b0c1f4-181a-4add-b3d2-5e11667e6172", + "pk": "9c7749d9-e519-44ec-8558-013f51bcff98", "fields": { - "element": "e59a3f12-2108-46d3-af15-ff5c48f498cf", + "element": "bd32400c-be2c-4fdf-bbb9-0166aad2e04b", "name": "number", "type": "text", "value": "1", @@ -1660,12 +1660,12 @@ }, { "model": "documents.metadata", - "pk": "b85d67e0-6601-43b3-a529-a6cf5a2decde", + "pk": "d9d22295-6f93-4741-b548-8fc253f0a5fb", "fields": { - "element": "bdc7c183-cf3c-440a-9feb-b9e6ea808106", - "name": "folio", + "element": "a22f1a4f-ee4e-44f4-8ea8-217f685b77d4", + "name": "number", "type": "text", - "value": "1r", + "value": "3", "entity": null, "worker_version": null, "worker_run": null @@ -1673,12 +1673,12 @@ }, { "model": "documents.metadata", - "pk": "bd6a37fc-2306-4099-9b4f-c74dbe3eeab5", + "pk": "de76c9c1-0151-4b06-afcd-b44621414de1", "fields": { - "element": "6fb960a2-8a10-4b4b-a805-cac9fc057d32", - "name": "number", + "element": "b1fef6ad-84b9-4590-82dc-7d5e94f51772", + "name": "folio", "type": "text", - "value": "2", + "value": "1v", "entity": null, "worker_version": null, "worker_run": null @@ -1686,12 +1686,12 @@ }, { "model": "documents.metadata", - "pk": "c2a22ddf-8c47-493b-bcb0-6a07eeb945a0", + "pk": "de83d6be-9880-48fd-bbe7-2d171c0893a2", "fields": { - "element": "d552e339-359a-4a06-bedf-13ee89919f53", + "element": "ad6483f2-c90a-41ba-9dbd-9cd3dbf78146", "name": "folio", "type": "text", - "value": "1r", + "value": "2r", "entity": null, "worker_version": null, "worker_run": null @@ -1714,12 +1714,12 @@ }, { "model": "images.image", - "pk": "02a73e92-f7d8-4356-ad2d-d6dd738c7bf7", + "pk": "2ee6f6af-ee3e-4ea8-86b6-d4bb0e4514ee", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img1", + "path": "img4", "width": 1000, "height": 1000, "hash": null, @@ -1728,12 +1728,12 @@ }, { "model": "images.image", - "pk": "17e5e746-3307-4bf2-a359-663e12bcbc17", + "pk": "3ce03720-068d-45f0-9237-df2d744ef953", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img6", + "path": "img5", "width": 1000, "height": 1000, "hash": null, @@ -1742,12 +1742,12 @@ }, { "model": "images.image", - "pk": "45cfd7e6-763e-4429-b92c-092ea0eb252b", + "pk": "71b96a29-ad88-4906-abeb-fd4a50d61af1", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img3", + "path": "img1", "width": 1000, "height": 1000, "hash": null, @@ -1756,7 +1756,7 @@ }, { "model": "images.image", - "pk": "a47f3bc7-d6e0-4810-beec-6f57bc61b186", + "pk": "749d041a-c431-4617-85ff-a0628f38737d", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", @@ -1770,12 +1770,12 @@ }, { "model": "images.image", - "pk": "b4cca2c4-04dc-4c7c-a2b6-fba4bc95ce45", + "pk": "7841d614-be5e-4ff6-82dd-4089cc644a19", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img5", + "path": "img6", "width": 1000, "height": 1000, "hash": null, @@ -1784,12 +1784,12 @@ }, { "model": "images.image", - "pk": "bb20838d-6fa5-4e64-93fc-c76d4248934e", + "pk": "9f324936-0561-432a-8985-103b240e55ed", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "server": 1, - "path": "img4", + "path": "img3", "width": 1000, "height": 1000, "hash": null, @@ -1798,45 +1798,56 @@ }, { "model": "users.right", - "pk": "1298f4db-5847-4432-8217-ab0ec2a1d438", + "pk": "056e0095-c923-4ad6-9c82-cbc4fd03be58", "fields": { - "user": 3, + "user": 2, "group": null, "content_type": 35, - "content_id": "7ebe6fc2-4ff5-4a0f-9d75-78076c835593", - "level": 50 + "content_id": "735c70c6-bb6f-405e-8126-c01efd6ad747", + "level": 100 } }, { "model": "users.right", - "pk": "409142e1-8037-4918-b569-14fa6878d9de", + "pk": "1b17d60e-bbc1-4116-bb4b-bb0cd2a65b0e", "fields": { - "user": 2, + "user": 4, "group": null, "content_type": 35, - "content_id": "7ebe6fc2-4ff5-4a0f-9d75-78076c835593", - "level": 100 + "content_id": "735c70c6-bb6f-405e-8126-c01efd6ad747", + "level": 10 } }, { "model": "users.right", - "pk": "68e00ea8-4db6-4c40-a67a-876683eb4bc6", + "pk": "6b9658f5-5a20-4268-8d84-80f0a0adb551", "fields": { - "user": 4, + "user": 2, "group": null, - "content_type": 35, - "content_id": "7ebe6fc2-4ff5-4a0f-9d75-78076c835593", + "content_type": 12, + "content_id": "409b6859-63b9-41f2-8449-ba737bca6624", "level": 10 } }, { "model": "users.right", - "pk": "f55f964f-8cf9-40a8-a0a2-6f6b6ecf5d81", + "pk": "6bc35837-48e5-4bb5-bb41-642c5063b681", + "fields": { + "user": 3, + "group": null, + "content_type": 35, + "content_id": "735c70c6-bb6f-405e-8126-c01efd6ad747", + "level": 50 + } +}, +{ + "model": "users.right", + "pk": "cf11fab0-8fc9-41b5-9a96-6464cc1b5581", "fields": { "user": 2, "group": null, "content_type": 20, - "content_id": "95305bde-aa8b-476e-85e7-94f974cdb6ad", + "content_id": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", "level": 100 } }, @@ -1844,7 +1855,7 @@ "model": "users.user", "pk": 1, "fields": { - "password": "pbkdf2_sha256$390000$LWc4YFwXJ6WlBfpAhAazyR$aEIzbr2ZJoRm/HeFA9JlP3HcpIGIVptUgMMdpWdAPsU=", + "password": "pbkdf2_sha256$390000$ZuasXMuh9i4eX0Ou9Aq1bL$QHVkSsvmF0+j2920P9h8CdsIDM8tK8/Qnv5m1Bvqu8s=", "last_login": null, "email": "root@root.fr", "display_name": "Admin", @@ -1859,7 +1870,7 @@ "model": "users.user", "pk": 2, "fields": { - "password": "pbkdf2_sha256$390000$VMqrCMCuqLiLfXaxnQb35g$QYr+sdsSQ+kE3hfaBa+AuHj5kA1Bw5naZtP0znP5+/s=", + "password": "pbkdf2_sha256$390000$dtjfH2x1Y4Ux9pYBLOeuxJ$61IjrNARDyLvmRE/9jdfbc5jrcJBtv4hlGeNJJ4KV/o=", "last_login": null, "email": "user@user.fr", "display_name": "Test user", @@ -1902,7 +1913,7 @@ }, { "model": "users.group", - "pk": "7ebe6fc2-4ff5-4a0f-9d75-78076c835593", + "pk": "735c70c6-bb6f-405e-8126-c01efd6ad747", "fields": { "name": "User group", "public": false, @@ -1911,7 +1922,7 @@ }, { "model": "users.oauthcredentials", - "pk": "dce89e0f-6b0c-48f3-8fc0-dd6d465c4d1c", + "pk": "217e2e72-f917-46de-9760-0e140bec08eb", "fields": { "user": 2, "provider_url": "https://somewhere", @@ -3985,15 +3996,15 @@ }, { "model": "ponos.farm", - "pk": "293a8d14-2331-44fd-9d73-22364bab9871", + "pk": "409b6859-63b9-41f2-8449-ba737bca6624", "fields": { "name": "Wheat farm", - "seed": "dce8f207b89e71bf3383db49129e8076558a47a76e6dbf419da0429389f4e754" + "seed": "9092a1fa53b98ee55ab02cf1d5e5bdb4652f2d020163b520e6c8e9cee0b0d3b4" } }, { "model": "ponos.task", - "pk": "6288867f-52e6-4d2f-84ee-84918f89a245", + "pk": "ef80eff8-75a8-4fe6-8c7a-46300faaa342", "fields": { "run": 0, "depth": 0, @@ -4009,21 +4020,21 @@ "agent": null, "requires_gpu": false, "gpu": null, - "process": "dd72a1c6-bcb2-4395-963f-0061adf097a5", + "process": "9a08ea15-07be-4746-a0d9-7acca68e5c1b", "container": null, "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "expiry": "2100-12-31T23:59:59.999Z", "extra_files": "{}", - "token": "WIIhMNuQT7+UC2QGGh3gWtM7FXHupUbnvLfwqrrGYzI=", + "token": "GoYSPBE0SraH8hiWN3QuGiDsG4Y+V0FVsNw3Q3h9J14=", "parents": [] } }, { "model": "ponos.artifact", - "pk": "f0677aad-c590-4b9a-a778-de8ea233258f", + "pk": "a93301c3-bdac-4d06-ba22-b7066decd282", "fields": { - "task": "6288867f-52e6-4d2f-84ee-84918f89a245", + "task": "ef80eff8-75a8-4fe6-8c7a-46300faaa342", "path": "/path/to/docker_build", "size": 42000, "content_type": "application/octet-stream", @@ -4033,11 +4044,11 @@ }, { "model": "training.dataset", - "pk": "8345563f-e0f4-4082-a515-94f5cffad55d", + "pk": "b7b3b1fa-b62a-4513-b99d-d4675e42525c", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", "creator": 2, "task": null, "name": "Second Dataset", @@ -4048,11 +4059,11 @@ }, { "model": "training.dataset", - "pk": "9628bec6-0971-4ae0-856a-a9418216ce91", + "pk": "bf46bc61-d37c-49ba-9368-899de7524287", "fields": { "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", - "corpus": "95305bde-aa8b-476e-85e7-94f974cdb6ad", + "corpus": "76968a0c-d43f-4ba1-8cd0-0f7a0dcdaa68", "creator": 2, "task": null, "name": "First Dataset", diff --git a/arkindex/documents/management/commands/build_fixtures.py b/arkindex/documents/management/commands/build_fixtures.py index 13c598d8d7e628181b7ce112865517a386ba941a..6727f672d7c447eaa640c92a8edbc5843ea5ce3d 100644 --- a/arkindex/documents/management/commands/build_fixtures.py +++ b/arkindex/documents/management/commands/build_fixtures.py @@ -111,8 +111,10 @@ class Command(BaseCommand): import_worker_type = WorkerType.objects.create(slug="import", display_name="Import") custom_worker_type = WorkerType.objects.create(slug="custom", display_name="Custom") - # Create a fake docker build with a docker image task farm = Farm.objects.create(name="Wheat farm") + farm.memberships.create(user=user, level=Role.Guest.value) + + # Create a fake docker build with a docker image task build_process = Process.objects.create( farm=farm, creator=superuser, @@ -241,6 +243,7 @@ class Command(BaseCommand): # Create a Workers process with worker runs on this corpus process = Process.objects.create( + farm=farm, mode=ProcessMode.Workers, creator=user, corpus=corpus, diff --git a/arkindex/documents/tests/tasks/test_corpus_delete.py b/arkindex/documents/tests/tasks/test_corpus_delete.py index 718e5f3fd51604f000313b80fbf0fe52f9bedc7b..3f112f1005fe3cfb3f8b42e39cb5322a894572a7 100644 --- a/arkindex/documents/tests/tasks/test_corpus_delete.py +++ b/arkindex/documents/tests/tasks/test_corpus_delete.py @@ -2,6 +2,7 @@ from django.db.models.signals import pre_delete from arkindex.documents.models import Corpus, Element, EntityType, MetaType, Transcription from arkindex.documents.tasks import corpus_delete +from arkindex.ponos.models import Farm from arkindex.process.models import CorpusWorkerVersion, ProcessMode, Repository, WorkerVersion from arkindex.project.tests import FixtureTestCase, force_constraints_immediate from arkindex.training.models import Dataset @@ -20,6 +21,7 @@ class TestDeleteCorpus(FixtureTestCase): creator=cls.user, mode=ProcessMode.Files, folder_type=cls.corpus.types.get(slug='volume'), + farm=Farm.objects.first(), ) file_import.files.create( corpus=cls.corpus, diff --git a/arkindex/documents/tests/test_metadata.py b/arkindex/documents/tests/test_metadata.py index fd9d26d45db40aeff288bb4f93785fae76bff4b2..5b8d6461a0b48fdfbae31db5012969d487b35d49 100644 --- a/arkindex/documents/tests/test_metadata.py +++ b/arkindex/documents/tests/test_metadata.py @@ -4,6 +4,7 @@ from django.urls import reverse from rest_framework import status from arkindex.documents.models import AllowedMetaData, Corpus, EntityType, MetaData, MetaType +from arkindex.ponos.models import Farm from arkindex.process.models import ProcessMode, WorkerRun, WorkerVersion from arkindex.project.tests import FixtureAPITestCase from arkindex.users.models import Role @@ -37,7 +38,11 @@ class TestMetaData(FixtureAPITestCase): cls.person_type = EntityType.objects.get(name='person', corpus=cls.corpus) cls.location_type = EntityType.objects.get(name='location', corpus=cls.corpus) - cls.process = cls.corpus.processes.create(mode=ProcessMode.Workers, creator=cls.user) + cls.process = cls.corpus.processes.create( + mode=ProcessMode.Workers, + creator=cls.user, + farm=Farm.objects.first(), + ) cls.process.worker_runs.create(version=cls.worker_version, parents=[]) cls.process.run() cls.task = cls.process.tasks.first() diff --git a/arkindex/documents/tests/test_retrieve_elements.py b/arkindex/documents/tests/test_retrieve_elements.py index b0694c8c20f219a8951a8080e026ff0469a15efa..5aad19acf47f75cc9d4e5104944ddfe5f1f215f1 100644 --- a/arkindex/documents/tests/test_retrieve_elements.py +++ b/arkindex/documents/tests/test_retrieve_elements.py @@ -3,6 +3,7 @@ from django.urls import reverse from rest_framework import status from arkindex.documents.models import Corpus, MLClass +from arkindex.ponos.models import Farm from arkindex.process.models import Process, ProcessMode, WorkerRun, WorkerVersion from arkindex.project.tests import FixtureAPITestCase from arkindex.users.models import Role @@ -122,6 +123,7 @@ class TestRetrieveElements(FixtureAPITestCase): revision=self.worker_version.revision, creator=self.user, generate_thumbnails=True, + farm=Farm.objects.first(), ) process.run() task = process.tasks.get() @@ -153,6 +155,7 @@ class TestRetrieveElements(FixtureAPITestCase): mode=ProcessMode.Repository, revision=self.worker_version.revision, creator=self.user, + farm=Farm.objects.first(), ) process.run() task = process.tasks.get() diff --git a/arkindex/images/tests/test_image_api.py b/arkindex/images/tests/test_image_api.py index f9b11cdeb81cc7c802e5bc9412d0ca0daad868fc..c9c8cc33df7362b842891055e838e840e34ede34 100644 --- a/arkindex/images/tests/test_image_api.py +++ b/arkindex/images/tests/test_image_api.py @@ -7,6 +7,7 @@ from django.urls import reverse from rest_framework import status from arkindex.images.models import Image, ImageServer +from arkindex.ponos.models import Farm from arkindex.process.models import Process, ProcessMode, Revision from arkindex.project.aws import S3FileStatus from arkindex.project.tests import FixtureAPITestCase @@ -610,6 +611,7 @@ class TestImageApi(FixtureAPITestCase): mode=ProcessMode.Repository, revision=Revision.objects.first(), creator=self.user, + farm=Farm.objects.first(), ) process.run() task = process.tasks.get() @@ -656,6 +658,7 @@ class TestImageApi(FixtureAPITestCase): mode=ProcessMode.Repository, revision=Revision.objects.first(), creator=self.user, + farm=Farm.objects.first(), ) process.run() task = process.tasks.get() diff --git a/arkindex/ponos/admin.py b/arkindex/ponos/admin.py index 714c26b87216e70bf020872c0a2ac64e1c71089c..474b809baa905d2b332c08a11d03ba235aeb15cf 100644 --- a/arkindex/ponos/admin.py +++ b/arkindex/ponos/admin.py @@ -5,6 +5,7 @@ from enumfields.admin import EnumFieldListFilter from arkindex.ponos.keys import gen_nonce from arkindex.ponos.models import FINAL_STATES, GPU, Agent, Artifact, Farm, Secret, Task, encrypt +from arkindex.users.admin import GroupMembershipInline, UserMembershipInline class ArtifactInline(admin.TabularInline): @@ -178,6 +179,7 @@ class FarmAdmin(admin.ModelAdmin): list_display = ("id", "name") fields = ("id", "name", "seed") readonly_fields = ("id",) + inlines = [UserMembershipInline, GroupMembershipInline] class ClearTextSecretForm(forms.ModelForm): diff --git a/arkindex/ponos/api.py b/arkindex/ponos/api.py index e46222184244a3b9808f2ba41d25adde1a5a9c63..1a5619b73635b262df792db41602acf401ec7358 100644 --- a/arkindex/ponos/api.py +++ b/arkindex/ponos/api.py @@ -442,6 +442,9 @@ class FarmList(ListAPIView): Cannot be used with Ponos agent or task authentication. """ serializer_class = FarmSerializer - queryset = Farm.objects.order_by("name") + queryset = Farm.objects.none() authentication_classes = (TokenAuthentication, SessionAuthentication) permission_classes = (IsVerified, ) + + def get_queryset(self): + return Farm.objects.available(self.request.user).order_by('name') diff --git a/arkindex/ponos/managers.py b/arkindex/ponos/managers.py index 43990999f5e2e49c5d5b344c06410613c309a9a3..2909e93075a7e337dc1a36dd92b955a2499118dc 100644 --- a/arkindex/ponos/managers.py +++ b/arkindex/ponos/managers.py @@ -1,5 +1,8 @@ from django.db.models import Manager +from arkindex.users.managers import BaseACLManager +from arkindex.users.models import Role + class TaskManager(Manager): def parents(self, task): @@ -22,3 +25,22 @@ class TaskManager(Manager): """, [task.id], ) + + +class FarmManager(BaseACLManager): + + def available(self, user): + """ + Farms that are visible to the user, and where process can run. + """ + # Without authentication, or as a Ponos agent, nothing is available. + # Agents are not users, filtering on them will make Django raise exceptions, so we have to handle them separately. + if user.is_anonymous or getattr(user, 'is_agent', False): + return self.none() + + # All farms are available to instance admins + if user.is_admin: + return self.all() + + # Regular users need guest access to each farm + return self.filter(id__in=self.filter_rights(user, self.model, Role.Guest.value).values('id')) diff --git a/arkindex/ponos/models.py b/arkindex/ponos/models.py index bcf831a13b640de6930f9593b798961df78c407b..e85e87c254f6038b1a6c0f70478c127f530690e6 100644 --- a/arkindex/ponos/models.py +++ b/arkindex/ponos/models.py @@ -10,6 +10,7 @@ from hashlib import sha256 from botocore.exceptions import ClientError from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from django.conf import settings +from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.fields import HStoreField from django.core.exceptions import ValidationError from django.core.validators import MinLengthValidator, MinValueValidator, RegexValidator @@ -20,9 +21,10 @@ from django.utils import timezone from enumfields import Enum, EnumField from arkindex.ponos.keys import gen_nonce -from arkindex.ponos.managers import TaskManager +from arkindex.ponos.managers import FarmManager, TaskManager from arkindex.project.aws import S3FileMixin from arkindex.project.validators import MaxValueValidator +from arkindex.users.models import Role from rest_framework_simplejwt.tokens import RefreshToken # Maximum allowed time until an agent is considered inactive since last request @@ -94,6 +96,9 @@ class Farm(models.Model): default=generate_seed, validators=[RegexValidator(r"^[0-9a-f]{64}$")], ) + memberships = GenericRelation('users.Right', 'content_id') + + objects = FarmManager() class Meta: constraints = [ @@ -106,6 +111,21 @@ class Farm(models.Model): def __str__(self) -> str: return "Farm {}".format(self.name) + def is_available(self, user) -> bool: + """ + Whether this farm is visible to the user and can be used to run processes. + """ + if user.is_anonymous or getattr(user, 'is_agent', False): + return False + + if user.is_admin: + return True + + from arkindex.users.utils import get_max_level + level = get_max_level(user, self) + + return level is not None and level >= Role.Guest.value + class Agent(models.Model): """ diff --git a/arkindex/ponos/tests/test_api.py b/arkindex/ponos/tests/test_api.py index 60c2092f15f266479e8f17ac620600dde78d16da..0e8d7549b509bb11cdbd0bb2b7b8796b6023e1e6 100644 --- a/arkindex/ponos/tests/test_api.py +++ b/arkindex/ponos/tests/test_api.py @@ -71,7 +71,8 @@ class TestAPI(FixtureAPITestCase): cls.process2 = Process.objects.create( mode=ProcessMode.Workers, creator=new_user, - corpus=new_corpus + corpus=new_corpus, + farm=cls.default_farm, ) cls.process2.run() cls.task4 = cls.process2.tasks.first() @@ -2564,10 +2565,12 @@ class TestAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_list_farms(self): - self.client.force_login(self.user) + self.client.force_login(self.superuser) + with self.assertNumQueries(4): response = self.client.get(reverse("api:farm-list")) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual( response.json(), { @@ -2582,6 +2585,31 @@ class TestAPI(FixtureAPITestCase): }, ) + def test_list_farms_guest(self): + self.client.force_login(self.user) + self.assertFalse(self.default_farm.is_available(self.user)) + self.assertTrue(self.wheat_farm.is_available(self.user)) + # Accessing memberships causes content types to be cached, + # so clear again to get the amount of queries for the worst case + self.clear_caches() + + with self.assertNumQueries(6): + response = self.client.get(reverse("api:farm-list")) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertDictEqual( + response.json(), + { + "count": 1, + "number": 1, + "previous": None, + "next": None, + "results": [ + {"id": str(self.wheat_farm.id), "name": "Wheat farm"}, + ], + }, + ) + @override_settings(PONOS_PRIVATE_KEY=PONOS_PRIVATE_KEY) def test_public_key(self): expected = load_pem_private_key( diff --git a/arkindex/ponos/tests/test_artifacts_api.py b/arkindex/ponos/tests/test_artifacts_api.py index 3a94095639f74a4f2d9a096a04b315d827ebed52..662127688a3f235ea32a50c90ac8953eb3ba9688 100644 --- a/arkindex/ponos/tests/test_artifacts_api.py +++ b/arkindex/ponos/tests/test_artifacts_api.py @@ -56,7 +56,8 @@ class TestAPI(FixtureAPITestCase): cls.process2 = Process.objects.create( mode=ProcessMode.Workers, creator=new_user, - corpus=new_corpus + corpus=new_corpus, + farm=cls.wheat_farm, ) cls.process2.run() cls.task3 = cls.process2.tasks.first() diff --git a/arkindex/ponos/tests/test_models.py b/arkindex/ponos/tests/test_models.py index 89af09017d2a04f6caa7ccc48928a7afe9c33713..eecba0b4b00b823e1d462b74ed9fe935761e9e8f 100644 --- a/arkindex/ponos/tests/test_models.py +++ b/arkindex/ponos/tests/test_models.py @@ -16,7 +16,11 @@ class TestModels(FixtureAPITestCase): def setUpTestData(cls): super().setUpTestData() cls.farm = Farm.objects.create(name="Cryptominers") - cls.process = cls.corpus.processes.create(creator=cls.user, mode=ProcessMode.Workers) + cls.process = cls.corpus.processes.create( + creator=cls.user, + mode=ProcessMode.Workers, + farm=cls.farm, + ) cls.process.run() cls.task1 = cls.process.tasks.first() cls.nonce = b"42" + b"0" * 14 diff --git a/arkindex/process/api.py b/arkindex/process/api.py index bdfdedc7a1b3656a89288f280be58adc743df6b0..20bbb748b8bcc9249abb25f4ebb12f4798c234b5 100644 --- a/arkindex/process/api.py +++ b/arkindex/process/api.py @@ -405,7 +405,7 @@ class ProcessDetails(ProcessACLMixin, ProcessQuerysetMixin, RetrieveUpdateDestro class ProcessRetry(ProcessACLMixin, ProcessQuerysetMixin, GenericAPIView): """ Retry a process. Can only be used on processes with Error, Failed, Stopped or Completed states.\n\n - Requires an **admin** access to the process. + Requires an **admin** access to the process and **guest** access to the process' farm. """ permission_classes = (IsVerified, ) serializer_class = ProcessSerializer @@ -419,6 +419,10 @@ class ProcessRetry(ProcessACLMixin, ProcessQuerysetMixin, GenericAPIView): if access_level < Role.Admin.value: raise PermissionDenied(detail='You do not have an admin access to this process.') + # When process.farm is None, the ProcessBuilder will already return an error, so we can ignore that case + if process.farm and not process.farm.is_available(self.request.user): + raise ValidationError({'farm': ['You do not have access to this farm.']}) + # process.state can cause new SQL queries to be run, so we access it just once state = process.state # Allow 'retrying' a process that has no Ponos tasks (that has never been started) @@ -459,37 +463,16 @@ class FilesProcess(CreateAPIView): permission_classes = (IsVerified, ) serializer_class = FilesProcessSerializer - def create(self, *args, **kwargs): - super().create(*args, **kwargs) + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) return Response( status=status.HTTP_201_CREATED, - data=ProcessSerializer(self.process, context={'request': self.request}).data, - ) - - def perform_create(self, serializer): - files = serializer.validated_data['files'] - corpus = files[0].corpus - mode = serializer.validated_data['mode'] - # Serializer validation codes returns an element for 'folder_id' - folder = serializer.validated_data.get('folder_id') - folder_type = serializer.validated_data.get('folder_type') - element_type = serializer.validated_data['element_type'] - - if folder and folder.corpus_id != corpus.id: - # The files' corpus is already validated as writable - raise ValidationError({'__all__': ['Element and files are in different corpora']}) - - # Start a process with thumbnails generation - self.process = corpus.processes.create( - creator=self.request.user, - mode=mode, - element=folder, - folder_type=folder_type, - element_type=element_type, - generate_thumbnails=True, + data=ProcessSerializer(serializer.instance, context={'request': self.request}).data, + headers=headers, ) - self.process.files.set(files) - self.process.run() @extend_schema_view( diff --git a/arkindex/process/models.py b/arkindex/process/models.py index 97fc9010f934403fc35258a94b0eb06a3665125f..864c14981b932c79c771c83ef5b7d5b8fc551e08 100644 --- a/arkindex/process/models.py +++ b/arkindex/process/models.py @@ -18,7 +18,6 @@ from arkindex.ponos.models import FINAL_STATES, STATES_ORDERING, Artifact, State from arkindex.process.builder import ProcessBuilder from arkindex.process.managers import ActivityManager, CorpusWorkerVersionManager, WorkerManager, WorkerVersionManager from arkindex.process.providers import get_provider -from arkindex.process.utils import get_default_farm_id from arkindex.project.aws import S3FileMixin, S3FileStatus from arkindex.project.fields import ArrayField, MD5HashField from arkindex.project.models import IndexableModel @@ -416,10 +415,6 @@ class Process(IndexableModel): """ Build and start a new run for this process. """ - # Use the default Ponos farm if no farm is specified - if not self.farm_id: - self.farm_id = get_default_farm_id() - process_builder = ProcessBuilder(self) process_builder.validate() process_builder.build() diff --git a/arkindex/process/providers.py b/arkindex/process/providers.py index 4bc977910df8f086c9152a4e36f4188fea1dd72a..0d51f8539814c6fc5ff3400cd9b055a469873624 100644 --- a/arkindex/process/providers.py +++ b/arkindex/process/providers.py @@ -9,6 +9,8 @@ from django.urls import reverse from gitlab import Gitlab, GitlabCreateError, GitlabGetError from rest_framework.exceptions import APIException, AuthenticationFailed, NotAuthenticated, ValidationError +from arkindex.process.utils import get_default_farm + logger = logging.getLogger(__name__) @@ -101,7 +103,16 @@ class GitProvider(ABC): # Limit the name length to the max size of Process name field name = f"Import {refs} from {rev.repo.url}"[:100] - process = rev.processes.create(creator=user, mode=mode, name=name) + farm = get_default_farm() + if not farm.is_available(user): + raise ValidationError("The owner of the OAuth credentials does not have access to the default farm.") + + process = rev.processes.create( + creator=user, + mode=mode, + name=name, + farm=farm, + ) process.run() return process diff --git a/arkindex/process/serializers/imports.py b/arkindex/process/serializers/imports.py index 122a25d6a2fcca1a517ce969459e7fdec7eee24d..6fd8f7c56b9ecca0882b9d09e72d1a795d78b1d0 100644 --- a/arkindex/process/serializers/imports.py +++ b/arkindex/process/serializers/imports.py @@ -18,6 +18,7 @@ from arkindex.process.models import ( WorkerVersionState, ) from arkindex.process.serializers.git import RevisionSerializer +from arkindex.process.utils import get_default_farm from arkindex.project.mixins import ProcessACLMixin from arkindex.project.serializer_fields import EnumField, LinearRingField from arkindex.project.validators import MaxValueValidator @@ -238,16 +239,32 @@ class ProcessListSerializer(ProcessLightSerializer): } -class FilesProcessSerializer(serializers.Serializer): +class FilesProcessSerializer(serializers.ModelSerializer): mode = EnumField(ProcessMode, default=ProcessMode.Files) files = serializers.PrimaryKeyRelatedField( queryset=DataFile.objects.select_related('corpus'), many=True, ) - folder_id = serializers.UUIDField(required=False, allow_null=True) + folder_id = serializers.UUIDField( + required=False, + allow_null=True, + source='element', + ) folder_type = serializers.SlugField(required=False, allow_null=True) element_type = serializers.SlugField() + farm_id = serializers.PrimaryKeyRelatedField( + queryset=Farm.objects.all(), + allow_null=True, + default=None, + source='farm', + ) + + # Automatically set the creator on the process + creator = serializers.HiddenField(default=serializers.CurrentUserDefault()) + + # Always start the process with thumbnail generation enabled + generate_thumbnails = serializers.HiddenField(default=True) default_error_messages = { 'mode_not_allowed': 'This mode is not allowed when importing from files', @@ -262,8 +279,22 @@ class FilesProcessSerializer(serializers.Serializer): 'type_not_found': 'Element type {slug!r} does not exist', 'type_not_folder': 'Element type {slug!r} is not a folder', 'type_folder': 'Element type {slug!r} is a folder', + 'wrong_folder_corpus': 'Element and files are in different corpora', } + class Meta: + model = Process + fields = ( + 'mode', + 'files', + 'folder_id', + 'folder_type', + 'element_type', + 'farm_id', + 'creator', + 'generate_thumbnails', + ) + def validate_mode(self, mode): if mode not in (ProcessMode.Files, ProcessMode.IIIF): self.fail('mode_not_allowed') @@ -291,6 +322,15 @@ class FilesProcessSerializer(serializers.Serializer): except Element.DoesNotExist: self.fail('folder_not_found') + def validate_farm_id(self, farm): + if farm is None: + farm = get_default_farm() + + if not farm.is_available(self.context['request'].user): + raise ValidationError(['You do not have access to this farm.']) + + return farm + def validate(self, data): if data['mode'] == ProcessMode.Files: if not all( @@ -307,18 +347,20 @@ class FilesProcessSerializer(serializers.Serializer): else: raise NotImplementedError + data['corpus'] = corpus = data['files'][0].corpus + element_type_slug = data.get('element_type') - element_type = data['files'][0].corpus.types.filter(slug=element_type_slug).first() + element_type = corpus.types.filter(slug=element_type_slug).first() if not element_type: self.fail('type_not_found', slug=element_type_slug) if element_type.folder: self.fail('type_folder', slug=element_type_slug) data['element_type'] = element_type - folder_type_slug, folder_id = data.get('folder_type'), data.get('folder_id') + folder_type_slug, folder = data.get('folder_type'), data.get('element') if folder_type_slug: - folder_type = data['files'][0].corpus.types.filter(slug=folder_type_slug).first() + folder_type = corpus.types.filter(slug=folder_type_slug).first() if not folder_type: self.fail('type_not_found', slug=folder_type_slug) if not folder_type.folder: @@ -328,12 +370,22 @@ class FilesProcessSerializer(serializers.Serializer): if data['mode'] == ProcessMode.IIIF: # folder_type is required in IIIF self.fail('iiif_folder_required') - if not folder_id: + if not folder: # Either folder_type or folder_id are required for other imports self.fail('folder_required') + # When a folder is set, it must be in the same corpus as all files + if folder.corpus_id != corpus.id: + self.fail('wrong_folder_corpus') return data + def create(self, validated_data): + files = validated_data.pop('files') + process = super().create(validated_data) + process.files.set(files) + process.run() + return process + class StartProcessSerializer(serializers.Serializer): chunks = serializers.IntegerField( @@ -342,11 +394,26 @@ class StartProcessSerializer(serializers.Serializer): default=1, ) thumbnails = serializers.BooleanField(default=False, source='generate_thumbnails') - farm = serializers.PrimaryKeyRelatedField(queryset=Farm.objects.all(), required=False, allow_null=True) + farm = serializers.PrimaryKeyRelatedField(queryset=Farm.objects.all(), default=None, allow_null=True) use_cache = serializers.BooleanField(default=False) use_gpu = serializers.BooleanField(default=False, allow_null=True) worker_activity = serializers.BooleanField(default=False) + def validate_farm(self, farm): + """ + Using the `default=` on the farm field would allow the default farm + to be used without having access rights to it, so we do the ACL checks + here during validation. We thus do not filter the field's queryset, + as we would be duplicating the ACL checks otherwise. + """ + if farm is None: + farm = get_default_farm() + + if not farm.is_available(self.context['request'].user): + raise ValidationError(['You do not have access to this farm.']) + + return farm + def validate(self, validated_data): assert self.instance is not None, 'A Process instance is required for this serializer' errors = defaultdict(list) diff --git a/arkindex/process/serializers/ingest.py b/arkindex/process/serializers/ingest.py index b1623ac8dc8278934d8fb8f8449b8fa90bd98dff..c5d8c1f8046302417ef9dd2a044470752857e964 100644 --- a/arkindex/process/serializers/ingest.py +++ b/arkindex/process/serializers/ingest.py @@ -3,7 +3,9 @@ from collections import defaultdict from rest_framework import serializers from arkindex.documents.models import Corpus, Element, ElementType +from arkindex.ponos.models import Farm from arkindex.process.models import Process, ProcessMode +from arkindex.process.utils import get_default_farm class BucketSerializer(serializers.Serializer): @@ -41,6 +43,13 @@ class S3ImportSerializer(serializers.ModelSerializer): default='page', write_only=True, ) + farm_id = serializers.PrimaryKeyRelatedField( + queryset=Farm.objects.all(), + write_only=True, + allow_null=True, + default=None, + source='farm', + ) creator = serializers.HiddenField(default=serializers.CurrentUserDefault()) mode = serializers.HiddenField(default=ProcessMode.S3) @@ -54,6 +63,7 @@ class S3ImportSerializer(serializers.ModelSerializer): 'element_id', 'folder_type', 'element_type', + 'farm_id', 'creator', 'mode', ) @@ -72,6 +82,15 @@ class S3ImportSerializer(serializers.ModelSerializer): self.fields['corpus_id'].queryset = corpora self.fields['element_id'].queryset = Element.objects.filter(corpus__in=corpora) + def validate_farm_id(self, farm): + if farm is None: + farm = get_default_farm() + + if not farm.is_available(self.context['request'].user): + raise serializers.ValidationError(['You do not have access to this farm.']) + + return farm + def validate(self, data): data = super().validate(data) errors = defaultdict(list) diff --git a/arkindex/process/serializers/training.py b/arkindex/process/serializers/training.py index b79fa9e8676b8ec16679a03b12c2bc94a5f6de55..e50c1e9863a3712727ffd58aa2c631db2ce1a0b7 100644 --- a/arkindex/process/serializers/training.py +++ b/arkindex/process/serializers/training.py @@ -5,7 +5,7 @@ from rest_framework import serializers from rest_framework.exceptions import PermissionDenied, ValidationError from arkindex.documents.models import Corpus, Element -from arkindex.ponos.models import Task +from arkindex.ponos.models import Farm, Task from arkindex.process.models import ( Process, ProcessDataset, @@ -16,6 +16,7 @@ from arkindex.process.models import ( WorkerVersionGPUUsage, WorkerVersionState, ) +from arkindex.process.utils import get_default_farm from arkindex.project.mixins import ProcessACLMixin, TrainingModelMixin, WorkerACLMixin from arkindex.training.models import Dataset, Model, ModelVersion from arkindex.users.models import Role @@ -100,6 +101,12 @@ class StartTrainingSerializer(serializers.ModelSerializer, WorkerACLMixin, Train write_only=True, ) use_gpu = serializers.BooleanField(default=False, write_only=True) + farm_id = serializers.PrimaryKeyRelatedField( + queryset=Farm.objects.all(), + allow_null=True, + default=None, + source='farm', + ) # Assign the current user to the process and use Training mode mode = serializers.HiddenField(default=ProcessMode.Training) @@ -119,6 +126,7 @@ class StartTrainingSerializer(serializers.ModelSerializer, WorkerACLMixin, Train 'model_version_id', 'worker_configuration_id', 'use_gpu', + 'farm_id', 'mode', 'creator', ) @@ -163,6 +171,15 @@ class StartTrainingSerializer(serializers.ModelSerializer, WorkerACLMixin, Train raise ValidationError(errors) return version + def validate_farm_id(self, farm): + if farm is None: + farm = get_default_farm() + + if not farm.is_available(self.context['request'].user): + raise ValidationError(['You do not have access to this farm.']) + + return farm + def validate(self, data): data = super().validate(data) diff --git a/arkindex/process/tests/test_create_process.py b/arkindex/process/tests/test_create_process.py index 6a2acf9102ea7cd571d4512a7894299cc906f2f7..4cde776000788bbbe4ea3040c03ee4c5e8f0a635 100644 --- a/arkindex/process/tests/test_create_process.py +++ b/arkindex/process/tests/test_create_process.py @@ -6,7 +6,7 @@ from rest_framework import status from rest_framework.reverse import reverse from arkindex.documents.models import Corpus, Element -from arkindex.ponos.models import State +from arkindex.ponos.models import Farm, State from arkindex.process.models import ActivityState, Process, ProcessMode, WorkerActivity, WorkerVersion from arkindex.project.mixins import SelectionMixin from arkindex.project.tests import FixtureAPITestCase @@ -584,7 +584,7 @@ class TestCreateProcess(FixtureAPITestCase): self.assertFalse(self.corpus.worker_versions.exists()) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(22): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process_2.id)}), {'worker_activity': True}, @@ -675,7 +675,7 @@ class TestCreateProcess(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(19): + with self.assertNumQueries(21): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process_2.id)}), {'use_cache': True}, @@ -723,7 +723,7 @@ class TestCreateProcess(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(19): + with self.assertNumQueries(21): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process_2.id)}), {'use_gpu': True}, @@ -758,7 +758,11 @@ class TestCreateProcess(FixtureAPITestCase): """ When a process is retried, the newly created tasks keep the same requires_gpu values """ - process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=Farm.objects.first(), + ) process.worker_runs.create( version=self.version_3, parents=[], @@ -790,7 +794,7 @@ class TestCreateProcess(FixtureAPITestCase): process.use_gpu = True process.save() self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process.id)}), {'use_gpu': 'true'} @@ -814,7 +818,7 @@ class TestCreateProcess(FixtureAPITestCase): process.use_gpu = True process.save() self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(22): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process.id)}), {'use_gpu': 'true'} diff --git a/arkindex/process/tests/test_create_s3_import.py b/arkindex/process/tests/test_create_s3_import.py index 5f5467e9a0d143f3a5b5bc069f1d6a1795579518..b8c9565e40d7e30e3878d2490270e4e3f11a778b 100644 --- a/arkindex/process/tests/test_create_s3_import.py +++ b/arkindex/process/tests/test_create_s3_import.py @@ -4,9 +4,10 @@ from rest_framework import status from arkindex.documents.models import Corpus from arkindex.images.models import ImageServer +from arkindex.ponos.models import Farm from arkindex.process.models import Process, ProcessMode, WorkerVersion from arkindex.project.tests import FixtureTestCase -from arkindex.users.models import Scope +from arkindex.users.models import Role, Scope class TestCreateS3Import(FixtureTestCase): @@ -15,6 +16,9 @@ class TestCreateS3Import(FixtureTestCase): def setUpTestData(cls): super().setUpTestData() cls.import_worker_version = WorkerVersion.objects.get(worker__slug='file_import') + cls.default_farm = Farm.objects.create(name='Crypto farm') + cls.default_farm.memberships.create(user=cls.user, level=Role.Guest.value) + cls.other_farm = Farm.objects.get(name='Wheat farm') def test_requires_login(self): with self.assertNumQueries(0): @@ -127,7 +131,7 @@ class TestCreateS3Import(FixtureTestCase): ImageServer.objects.create(id=999, display_name='Ingest image server', url='https://dev.null.teklia.com') element = self.corpus.elements.get(name='Volume 1') - with self.assertNumQueries(25), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(27), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse('api:s3-import-create'), { 'corpus_id': str(self.corpus.id), 'element_id': str(element.id), @@ -135,8 +139,9 @@ class TestCreateS3Import(FixtureTestCase): 'element_type': 'page', 'bucket_name': 'blah', 'prefix': 'a/b/c', + 'farm_id': str(self.other_farm.id), }) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.json()) data = response.json() process = Process.objects.get(id=data['id']) @@ -189,7 +194,7 @@ class TestCreateS3Import(FixtureTestCase): self.corpus.types.create(slug='folder', display_name='Folder', folder=True) ImageServer.objects.create(id=999, display_name='Ingest image server', url='https://dev.null.teklia.com') - with self.assertNumQueries(24), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(26), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse('api:s3-import-create'), { 'corpus_id': str(self.corpus.id), 'bucket_name': 'blah', @@ -201,6 +206,7 @@ class TestCreateS3Import(FixtureTestCase): self.assertDictEqual(data, {'id': str(process.id)}) self.assertEqual(process.mode, ProcessMode.S3) self.assertEqual(process.creator, self.user) + self.assertEqual(process.farm, self.default_farm) self.assertEqual(process.corpus_id, self.corpus.id) self.assertIsNone(process.element_id) self.assertEqual(process.folder_type, self.corpus.types.get(slug='folder')) @@ -229,3 +235,44 @@ class TestCreateS3Import(FixtureTestCase): 'INGEST_S3_ACCESS_KEY': '🔑', 'INGEST_S3_SECRET_KEY': 'its-secret-i-wont-tell-you', }) + + def test_farm_guest(self): + self.user.user_scopes.create(scope=Scope.S3Ingest) + self.client.force_login(self.user) + self.corpus.types.create(slug='folder', display_name='Folder', folder=True) + ImageServer.objects.create(id=999, display_name='Ingest image server', url='https://dev.null.teklia.com') + + self.other_farm.memberships.filter(user=self.user).delete() + + with self.assertNumQueries(8), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + response = self.client.post(reverse('api:s3-import-create'), { + 'corpus_id': str(self.corpus.id), + 'bucket_name': 'blah', + 'farm_id': str(self.other_farm.id), + }) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertDictEqual(response.json(), { + 'farm_id': ['You do not have access to this farm.'], + }) + self.assertFalse(Process.objects.filter(mode=ProcessMode.S3).exists()) + + def test_default_farm_guest(self): + self.user.user_scopes.create(scope=Scope.S3Ingest) + self.client.force_login(self.user) + self.corpus.types.create(slug='folder', display_name='Folder', folder=True) + ImageServer.objects.create(id=999, display_name='Ingest image server', url='https://dev.null.teklia.com') + + self.default_farm.memberships.filter(user=self.user).delete() + + with self.assertNumQueries(8), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + response = self.client.post(reverse('api:s3-import-create'), { + 'corpus_id': str(self.corpus.id), + 'bucket_name': 'blah', + }) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertDictEqual(response.json(), { + 'farm_id': ['You do not have access to this farm.'], + }) + self.assertFalse(Process.objects.filter(mode=ProcessMode.S3).exists()) diff --git a/arkindex/process/tests/test_create_training_process.py b/arkindex/process/tests/test_create_training_process.py index 1557037fe4be95505ef5cfade1519f787dcba05e..6f6257642adde13b36bc3b2efec7ce38e01bdb67 100644 --- a/arkindex/process/tests/test_create_training_process.py +++ b/arkindex/process/tests/test_create_training_process.py @@ -5,7 +5,7 @@ from django.test import override_settings from django.urls import reverse from rest_framework import status -from arkindex.ponos.models import Artifact +from arkindex.ponos.models import Artifact, Farm from arkindex.ponos.models import State as PonosState from arkindex.process.models import ( Process, @@ -55,6 +55,10 @@ class TestCreateTrainingProcess(FixtureTestCase): size=1337, ) + cls.default_farm = Farm.objects.create(name='Crypto farm') + cls.default_farm.memberships.create(user=cls.user, level=Role.Guest.value) + cls.other_farm = Farm.objects.get(name='Wheat farm') + def setUp(self): super().setUp() # Base payload with the required fields @@ -73,7 +77,7 @@ class TestCreateTrainingProcess(FixtureTestCase): def test_required_parameters(self): self.client.force_login(self.user) - with self.assertNumQueries(2): + with self.assertNumQueries(6): response = self.client.post(reverse('api:process-training'), {}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), { @@ -95,7 +99,7 @@ class TestCreateTrainingProcess(FixtureTestCase): 'use_gpu', ] self.client.force_login(self.user) - with self.assertNumQueries(2): + with self.assertNumQueries(6): response = self.client.post( reverse('api:process-training'), # Cast every parameter as an integer @@ -116,7 +120,7 @@ class TestCreateTrainingProcess(FixtureTestCase): """ self.corpus.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(13): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), { @@ -130,7 +134,7 @@ class TestCreateTrainingProcess(FixtureTestCase): """ self.training_worker_version.worker.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(13): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), { @@ -140,7 +144,7 @@ class TestCreateTrainingProcess(FixtureTestCase): def test_model_right(self): self.model.memberships.filter(user=self.user).update(level=Role.Guest.value) self.client.force_login(self.user) - with self.assertNumQueries(10): + with self.assertNumQueries(13): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), { @@ -156,7 +160,7 @@ class TestCreateTrainingProcess(FixtureTestCase): size=333, ) self.client.force_login(self.user) - with self.assertNumQueries(12): + with self.assertNumQueries(15): response = self.client.post(reverse('api:process-training'), { **self.base_payload, 'model_version_id': str(other_model_version.id), @@ -169,7 +173,7 @@ class TestCreateTrainingProcess(FixtureTestCase): def test_folders_validation(self): self.client.force_login(self.user) train_id, valid_id, test_id = uuid.uuid4(), uuid.uuid4(), uuid.uuid4() - with self.assertNumQueries(13): + with self.assertNumQueries(16): response = self.client.post(reverse('api:process-training'), { **self.base_payload, 'train_folder_id': train_id, @@ -186,7 +190,7 @@ class TestCreateTrainingProcess(FixtureTestCase): def test_folders_similar(self): self.client.force_login(self.user) folder_id = self.base_payload['train_folder_id'] - with self.assertNumQueries(13): + with self.assertNumQueries(16): response = self.client.post(reverse('api:process-training'), { **self.base_payload, 'validation_folder_id': folder_id, @@ -206,7 +210,7 @@ class TestCreateTrainingProcess(FixtureTestCase): self.training_worker_version.state = state self.training_worker_version.save() - with self.assertNumQueries(11): + with self.assertNumQueries(14): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -221,7 +225,7 @@ class TestCreateTrainingProcess(FixtureTestCase): self.client.force_login(self.user) self.training_worker_version.gpu_usage = WorkerVersionGPUUsage.Required self.training_worker_version.save() - with self.assertNumQueries(11): + with self.assertNumQueries(14): response = self.client.post(reverse('api:process-training'), { **self.base_payload, 'use_gpu': False, @@ -238,7 +242,7 @@ class TestCreateTrainingProcess(FixtureTestCase): self.client.force_login(self.user) self.training_worker_version.gpu_usage = WorkerVersionGPUUsage.Disabled self.training_worker_version.save() - with self.assertNumQueries(11): + with self.assertNumQueries(14): response = self.client.post(reverse('api:process-training'), { **self.base_payload, 'use_gpu': True, @@ -255,7 +259,7 @@ class TestCreateTrainingProcess(FixtureTestCase): self.client.force_login(self.user) self.training_worker_version.docker_image_iid = None self.training_worker_version.save() - with self.assertNumQueries(11): + with self.assertNumQueries(14): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.json(), { @@ -271,13 +275,14 @@ class TestCreateTrainingProcess(FixtureTestCase): training_process = Process.objects.filter(name='Test training') self.assertEqual(training_process.count(), 0) - with self.assertNumQueries(23): + with self.assertNumQueries(25): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(training_process.count(), 1) training_process = training_process.get() self.assertEqual(training_process.creator_id, self.user.id) + self.assertEqual(training_process.farm, self.default_farm) self.assertEqual(response.json(), { 'id': str(training_process.id), @@ -329,7 +334,7 @@ class TestCreateTrainingProcess(FixtureTestCase): self.client.force_login(self.user) training_process = Process.objects.filter(name='Test training') self.assertEqual(training_process.count(), 0) - with self.assertNumQueries(22): + with self.assertNumQueries(24): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(training_process.count(), 1) @@ -342,7 +347,7 @@ class TestCreateTrainingProcess(FixtureTestCase): self.client.force_login(self.user) training_process = Process.objects.filter(name='Test training') self.assertEqual(training_process.count(), 0) - with self.assertNumQueries(22): + with self.assertNumQueries(24): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(training_process.count(), 1) @@ -354,7 +359,8 @@ class TestCreateTrainingProcess(FixtureTestCase): self.client.force_login(self.user) training_process = Process.objects.filter(name='Test training') self.assertEqual(training_process.count(), 0) - with self.assertNumQueries(27): + + with self.assertNumQueries(29): response = self.client.post(reverse('api:process-training'), { **self.base_payload, 'validation_folder_id': str(self.validation_folder.id), @@ -362,8 +368,10 @@ class TestCreateTrainingProcess(FixtureTestCase): 'model_version_id': str(self.model_version.id), 'worker_configuration_id': str(self.configuration.id), 'use_gpu': True, + 'farm_id': str(self.other_farm.id), }) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(training_process.count(), 1) training_process = training_process.get() self.assertEqual(response.json(), { @@ -390,6 +398,7 @@ class TestCreateTrainingProcess(FixtureTestCase): 'validation_folder_id': str(self.validation_folder.id), }) self.assertEqual(training_process.creator_id, self.user.id) + self.assertEqual(training_process.farm, self.other_farm) task = training_process.tasks.get() self.assertEqual(sorted(task.env.keys()), [ 'ARKINDEX_CORPUS_ID', @@ -415,7 +424,7 @@ class TestCreateTrainingProcess(FixtureTestCase): self.assertEqual(self.training_worker_version.docker_shm_size, '999G') self.training_worker_version.save() - with self.assertNumQueries(23): + with self.assertNumQueries(25): response = self.client.post(reverse('api:process-training'), self.base_payload) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -435,3 +444,34 @@ class TestCreateTrainingProcess(FixtureTestCase): self.assertEqual(task.image_artifact.id, self.training_worker_version.docker_image_id) self.assertEqual(task.requires_gpu, False) self.assertEqual(task.shm_size, '999G') + + def test_default_farm_guest(self): + self.default_farm.memberships.filter(user=self.user).delete() + self.client.force_login(self.user) + + with self.assertNumQueries(13): + response = self.client.post(reverse('api:process-training'), self.base_payload) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertDictEqual(response.json(), { + 'farm_id': ['You do not have access to this farm.'], + }) + + self.assertFalse(Process.objects.filter(name='Test training').exists()) + + def test_farm_guest(self): + self.other_farm.memberships.filter(user=self.user).delete() + self.client.force_login(self.user) + + with self.assertNumQueries(13): + response = self.client.post(reverse('api:process-training'), { + **self.base_payload, + 'farm_id': str(self.other_farm.id), + }) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertDictEqual(response.json(), { + 'farm_id': ['You do not have access to this farm.'], + }) + + self.assertFalse(Process.objects.filter(name='Test training').exists()) diff --git a/arkindex/process/tests/test_datafile_api.py b/arkindex/process/tests/test_datafile_api.py index a0425907487300665b48c4e29fcf2084a9aa6200..2379cee126043964a408e19d55aa30bd0dfeb4bb 100644 --- a/arkindex/process/tests/test_datafile_api.py +++ b/arkindex/process/tests/test_datafile_api.py @@ -4,6 +4,7 @@ from django.urls import reverse from rest_framework import status from arkindex.documents.models import Corpus +from arkindex.ponos.models import Farm from arkindex.process.models import DataFile, Process, ProcessMode, WorkerVersion from arkindex.project.aws import S3FileStatus from arkindex.project.tests import FixtureAPITestCase @@ -165,6 +166,7 @@ class TestDataFileApi(FixtureAPITestCase): corpus=self.corpus, creator=user, mode=ProcessMode.Workers, + farm=Farm.objects.first(), ) process.files.add(self.df) with self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): @@ -199,6 +201,7 @@ class TestDataFileApi(FixtureAPITestCase): corpus=self.corpus, creator=self.user, mode=ProcessMode.Files, + farm=Farm.objects.first(), ) process.files.add(self.df) with self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): diff --git a/arkindex/process/tests/test_docker_worker_version.py b/arkindex/process/tests/test_docker_worker_version.py index 83966e57ff1abb6a2048cd85bd1a6e3935c70ad7..9342f1f9f952ffbe66a0b913c3d5328de858a611 100644 --- a/arkindex/process/tests/test_docker_worker_version.py +++ b/arkindex/process/tests/test_docker_worker_version.py @@ -1,6 +1,7 @@ from django.urls import reverse from rest_framework import status +from arkindex.ponos.models import Farm from arkindex.process.models import GitRefType, ProcessMode, Repository, Worker, WorkerType, WorkerVersionGPUUsage from arkindex.project.tests import FixtureAPITestCase from arkindex.users.models import Role, Scope @@ -31,7 +32,11 @@ class TestDockerWorkerVersion(FixtureAPITestCase): """ Ponos task authentication is disabled on that endpoint """ - process = self.rev.processes.create(mode=ProcessMode.Repository, creator=self.user) + process = self.rev.processes.create( + mode=ProcessMode.Repository, + creator=self.user, + farm=Farm.objects.first(), + ) process.run() task = process.tasks.get() response = self.client.post( diff --git a/arkindex/process/tests/test_processes.py b/arkindex/process/tests/test_processes.py index 6cac6b4b19e0b451fd9915259c6ba3a2893e73d8..a37876f4383069ca033a355b5bca8f3b276f8122 100644 --- a/arkindex/process/tests/test_processes.py +++ b/arkindex/process/tests/test_processes.py @@ -18,7 +18,6 @@ from arkindex.process.models import ( WorkerVersion, WorkerVersionState, ) -from arkindex.process.utils import get_default_farm_id from arkindex.project.tests import FixtureAPITestCase from arkindex.training.models import Dataset, Model, ModelVersion, ModelVersionState from arkindex.users.models import Role, User @@ -32,7 +31,10 @@ class TestProcesses(FixtureAPITestCase): @classmethod def setUpTestData(cls): super().setUpTestData() - cls.farm = Farm.objects.get(name='Wheat farm') + # The default farm will be the first one in alphabetical order + cls.default_farm = Farm.objects.create(name='Corn farm') + cls.default_farm.memberships.create(user=cls.user, level=Role.Guest.value) + cls.other_farm = Farm.objects.get(name='Wheat farm') cls.creds = cls.user.credentials.get() cls.repo = cls.creds.repos.get(url='http://my_repo.fake/workers/worker') cls.rev = cls.repo.revisions.get() @@ -90,7 +92,7 @@ class TestProcesses(FixtureAPITestCase): mode=ProcessMode.Files, creator=cls.user, corpus=cls.private_corpus, - farm=cls.farm, + farm=cls.default_farm, ) cls.private_ml_class = cls.private_corpus.ml_classes.create(name='beignet') @@ -100,9 +102,14 @@ class TestProcesses(FixtureAPITestCase): mode=ProcessMode.Repository, creator=cls.user2, revision=cls.rev, + farm=cls.default_farm, ) # Admin access - cls.elts_process = cls.corpus.processes.create(mode=ProcessMode.Workers, creator=cls.user2) + cls.elts_process = cls.corpus.processes.create( + mode=ProcessMode.Workers, + creator=cls.user2, + farm=cls.default_farm, + ) cls.processes = Process.objects.filter( id__in=[cls.user_img_process.id, cls.repository_process.id, cls.elts_process.id] ) @@ -116,6 +123,7 @@ class TestProcesses(FixtureAPITestCase): creator=cls.user, corpus=cls.corpus, mode=ProcessMode.Training, + farm=cls.default_farm, model=cls.training_model, train_folder=cls.train, validation_folder=cls.val, @@ -744,8 +752,8 @@ class TestProcesses(FixtureAPITestCase): 'element_name_contains': None, 'element_type': None, 'farm': { - 'id': str(self.farm.id), - 'name': 'Wheat farm', + 'id': str(self.default_farm.id), + 'name': 'Corn farm', }, 'files': [], 'folder_type': None, @@ -792,7 +800,10 @@ class TestProcesses(FixtureAPITestCase): 'element': None, 'element_name_contains': None, 'element_type': None, - 'farm': None, + 'farm': { + 'id': str(self.default_farm.id), + 'name': 'Corn farm', + }, 'files': [], 'folder_type': None, 'id': str(self.training_process.id), @@ -998,7 +1009,11 @@ class TestProcesses(FixtureAPITestCase): A file import's element can be updated even while it is running """ self.client.force_login(self.user) - process = self.corpus.processes.create(mode=ProcessMode.Files, creator=self.user) + process = self.corpus.processes.create( + mode=ProcessMode.Files, + creator=self.user, + farm=self.default_farm, + ) with self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): process.run() process.tasks.update(state=State.Running) @@ -1400,7 +1415,7 @@ class TestProcesses(FixtureAPITestCase): }, format='json' ) - self.assertEqual(response.status_code, status.HTTP_200_OK, response.json()) + self.assertEqual(response.status_code, status.HTTP_200_OK) process.refresh_from_db() self.assertEqual(response.json(), { @@ -1706,7 +1721,7 @@ class TestProcesses(FixtureAPITestCase): with self.subTest(state=state): self.elts_process.tasks.all().update(state=state) self.assertEqual(self.elts_process.state, state) - with self.assertNumQueries(9): + with self.assertNumQueries(11): response = self.client.post(reverse('api:process-retry', kwargs={'pk': self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), {'__all__': [message]}) @@ -1720,7 +1735,7 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.finished = timezone.now() self.elts_process.save() - with self.assertNumQueries(14): + with self.assertNumQueries(16): response = self.client.post(reverse('api:process-retry', kwargs={'pk': self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1731,12 +1746,30 @@ class TestProcesses(FixtureAPITestCase): # Activity initialization runs again self.assertFalse(delay_mock.called) + def test_retry_farm_guest(self): + self.elts_process.run() + self.elts_process.tasks.all().update(state=State.Error) + self.elts_process.finished = timezone.now() + self.elts_process.save() + self.assertEqual(self.elts_process.state, State.Error) + + self.elts_process.farm.memberships.filter(user=self.user).delete() + self.client.force_login(self.user) + + with self.assertNumQueries(10): + response = self.client.post(reverse('api:process-retry', kwargs={'pk': self.elts_process.id})) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertEqual(response.json(), { + 'farm': ['You do not have access to this farm.'], + }) + @patch('arkindex.project.triggers.process_tasks.initialize_activity.delay') def test_retry_no_tasks(self, delay_mock): self.client.force_login(self.user) self.assertFalse(self.elts_process.tasks.exists()) - with self.assertNumQueries(14): + with self.assertNumQueries(15): response = self.client.post(reverse('api:process-retry', kwargs={'pk': self.elts_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1756,7 +1789,7 @@ class TestProcesses(FixtureAPITestCase): self.repo.memberships.filter(user=self.user).update(level=Role.Admin.value) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(10): response = self.client.post(reverse('api:process-retry', kwargs={'pk': self.repository_process.id})) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -1775,7 +1808,7 @@ class TestProcesses(FixtureAPITestCase): self.workers_process.activity_state = ActivityState.Error self.workers_process.save() - with self.assertNumQueries(16): + with self.assertNumQueries(18): response = self.client.post(reverse('api:process-retry', kwargs={'pk': self.workers_process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1787,7 +1820,11 @@ class TestProcesses(FixtureAPITestCase): def test_retry_files(self): self.client.force_login(self.user) - process = self.corpus.processes.create(mode=ProcessMode.Files, creator=self.user) + process = self.corpus.processes.create( + mode=ProcessMode.Files, + creator=self.user, + farm=self.default_farm, + ) process.worker_runs.create(version=self.version_with_model) process.tasks.create(state=State.Error, run=0, depth=0) self.assertEqual(process.state, State.Error) @@ -1795,7 +1832,7 @@ class TestProcesses(FixtureAPITestCase): with ( self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), - self.assertNumQueries(18), + self.assertNumQueries(19), ): response = self.client.post(reverse('api:process-retry', kwargs={'pk': process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1816,26 +1853,26 @@ class TestProcesses(FixtureAPITestCase): folder_type=self.volume_type, element_type=self.page_type, creator=self.user, + farm=self.default_farm, ) process.worker_runs.create(version=self.version_with_model) process.tasks.create(state=State.Error, run=0, depth=0) self.assertEqual(process.state, State.Error) process.finished = timezone.now() - with self.settings( - IMPORTS_WORKER_VERSION=str(self.version_with_model.id), - INGEST_IMAGESERVER_ID=str(ImageServer.objects.get(url='http://server').id), + # Make sure the import version and the image server ar not cached to count all queries. + # This can occur when running tests in a different order. + self.clear_caches() + + with ( + self.settings( + IMPORTS_WORKER_VERSION=str(self.version_with_model.id), + INGEST_IMAGESERVER_ID=str(ImageServer.objects.get(url='http://server').id), + ), + self.assertNumQueries(20), ): - # Make sure the import version and the image server ar not cached to count all queries. - # This can occur when running tests in a different order. - if hasattr(WorkerVersion.objects, 'imports_version'): - del WorkerVersion.objects.imports_version - if hasattr(ImageServer.objects, 'ingest'): - del ImageServer.objects.ingest - - with self.assertNumQueries(19): - response = self.client.post(reverse('api:process-retry', kwargs={'pk': process.id})) - self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.post(reverse('api:process-retry', kwargs={'pk': process.id})) + self.assertEqual(response.status_code, status.HTTP_200_OK) process.refresh_from_db() self.assertEqual(process.state, State.Unscheduled) @@ -1847,7 +1884,11 @@ class TestProcesses(FixtureAPITestCase): def test_retry_iiif(self): self.client.force_login(self.user) - process = self.corpus.processes.create(mode=ProcessMode.IIIF, creator=self.user) + process = self.corpus.processes.create( + mode=ProcessMode.IIIF, + creator=self.user, + farm=self.default_farm, + ) process.worker_runs.create(version=self.version_with_model) process.tasks.create(state=State.Error, run=0, depth=0) self.assertEqual(process.state, State.Error) @@ -1855,7 +1896,7 @@ class TestProcesses(FixtureAPITestCase): with ( self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), - self.assertNumQueries(18), + self.assertNumQueries(19), ): response = self.client.post(reverse('api:process-retry', kwargs={'pk': process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -1879,19 +1920,25 @@ class TestProcesses(FixtureAPITestCase): def test_from_files(self): self.client.force_login(self.user) self.assertEqual(self.version_with_model.worker_runs.count(), 0) - with self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)): + + with ( + self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), + self.assertNumQueries(30) + ): response = self.client.post(reverse('api:files-process'), { 'files': [str(self.img_df.id)], 'folder_type': 'volume', 'element_type': 'page', }, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + data = response.json() process = Process.objects.get(id=data['id']) self.assertEqual(process.mode, ProcessMode.Files) self.assertListEqual(list(process.files.all()), [self.img_df]) self.assertEqual(process.folder_type.slug, 'volume') self.assertEqual(process.element_type.slug, 'page') + self.assertEqual(process.farm, self.default_farm) self.assertEqual(self.version_with_model.worker_runs.count(), 1) import_task = process.tasks.get(slug="import_files") self.assertEqual( @@ -1920,7 +1967,7 @@ class TestProcesses(FixtureAPITestCase): def test_from_files_multiple_types(self): self.client.force_login(self.user) - with self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)), self.assertNumQueries(30): + with self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)), self.assertNumQueries(33): response = self.client.post(reverse('api:files-process'), { 'files': [ str(self.pdf_df.id), @@ -1955,7 +2002,7 @@ class TestProcesses(FixtureAPITestCase): def test_from_files_iiif(self): self.client.force_login(self.user) - with self.assertNumQueries(27), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): + with self.assertNumQueries(30), self.settings(IMPORTS_WORKER_VERSION=str(self.import_worker_version.id)): response = self.client.post(reverse('api:files-process'), { 'files': [str(self.iiif_df.id)], 'mode': 'iiif', @@ -2064,7 +2111,7 @@ class TestProcesses(FixtureAPITestCase): def test_from_files_files_wrong_type(self): self.client.force_login(self.user) - with self.assertNumQueries(6): + with self.assertNumQueries(9): response = self.client.post(reverse('api:files-process'), { 'files': [str(self.iiif_df.id)], 'folder_type': 'volume', @@ -2104,6 +2151,67 @@ class TestProcesses(FixtureAPITestCase): }, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + def test_from_files_farm(self): + self.client.force_login(self.user) + self.assertEqual(self.version_with_model.worker_runs.count(), 0) + + with ( + self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), + self.assertNumQueries(30) + ): + response = self.client.post(reverse('api:files-process'), { + 'files': [str(self.img_df.id)], + 'folder_type': 'volume', + 'element_type': 'page', + 'farm_id': str(self.other_farm.id), + }, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + data = response.json() + process = Process.objects.get(id=data['id']) + self.assertEqual(process.farm, self.other_farm) + + def test_from_files_farm_guest(self): + self.client.force_login(self.user) + self.assertEqual(self.version_with_model.worker_runs.count(), 0) + self.other_farm.memberships.filter(user=self.user).delete() + + with ( + self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), + self.assertNumQueries(8) + ): + response = self.client.post(reverse('api:files-process'), { + 'files': [str(self.img_df.id)], + 'folder_type': 'volume', + 'element_type': 'page', + 'farm_id': str(self.other_farm.id), + }, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertEqual(response.json(), { + 'farm_id': ['You do not have access to this farm.'], + }) + + def test_from_files_default_farm_guest(self): + self.client.force_login(self.user) + self.assertEqual(self.version_with_model.worker_runs.count(), 0) + self.default_farm.memberships.filter(user=self.user).delete() + + with ( + self.settings(IMPORTS_WORKER_VERSION=str(self.version_with_model.id)), + self.assertNumQueries(8) + ): + response = self.client.post(reverse('api:files-process'), { + 'files': [str(self.img_df.id)], + 'folder_type': 'volume', + 'element_type': 'page', + }, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertEqual(response.json(), { + 'farm_id': ['You do not have access to this farm.'], + }) + def test_start_process_requires_login(self): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(self.user_img_process.id)}) @@ -2133,7 +2241,11 @@ class TestProcesses(FixtureAPITestCase): ) def test_start_process_process_already_started(self): - process2 = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process2 = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.default_farm, + ) process2.run() self.assertTrue(process2.tasks.exists()) @@ -2148,7 +2260,11 @@ class TestProcesses(FixtureAPITestCase): ) def test_start_process_without_required_model(self): - process2 = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process2 = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.default_farm, + ) process2.worker_runs.create(version=self.version_with_model, parents=[], configuration=None) process2.save() @@ -2163,14 +2279,18 @@ class TestProcesses(FixtureAPITestCase): ) def test_start_process_with_required_model(self): - process2 = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process2 = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.default_farm, + ) run = process2.worker_runs.create(version=self.version_with_model, parents=[], configuration=None) run.model_version = self.model_version_1 run.save() self.assertFalse(process2.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(19): + with self.assertNumQueries(21): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2178,10 +2298,14 @@ class TestProcesses(FixtureAPITestCase): self.assertEqual(response.json()['id'], str(process2.id)) def test_start_process_empty(self): - process2 = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process2 = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.default_farm, + ) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2193,14 +2317,18 @@ class TestProcesses(FixtureAPITestCase): ) def test_start_process_unavailable_worker_version(self): - process2 = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process2 = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.default_farm, + ) process2.worker_runs.create(version=self.recognizer, parents=[], configuration=None) self.recognizer.state = WorkerVersionState.Error self.recognizer.save() self.assertFalse(process2.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2218,7 +2346,7 @@ class TestProcesses(FixtureAPITestCase): self.assertFalse(process2.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2267,7 +2395,7 @@ class TestProcesses(FixtureAPITestCase): ) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2291,7 +2419,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(19): + with self.assertNumQueries(21): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2301,7 +2429,7 @@ class TestProcesses(FixtureAPITestCase): process2.refresh_from_db() self.assertEqual(process2.state, State.Unscheduled) # Ensure default parameters are used - self.assertEqual(process2.farm_id, get_default_farm_id()) + self.assertEqual(process2.farm, self.default_farm) self.assertEqual(process2.tasks.count(), 2) task1, task2 = process2.tasks.order_by('slug') self.assertEqual(task1.slug, 'initialisation') @@ -2315,7 +2443,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2332,7 +2460,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2348,7 +2476,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}), { @@ -2373,7 +2501,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(17): + with self.assertNumQueries(19): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process2.id)}) ) @@ -2383,7 +2511,7 @@ class TestProcesses(FixtureAPITestCase): process2.refresh_from_db() self.assertEqual(process2.state, State.Unscheduled) # Ensure default parameters are used - self.assertEqual(process2.farm_id, get_default_farm_id()) + self.assertEqual(process2.farm, self.default_farm) self.assertEqual(process2.tasks.count(), 1) task = process2.tasks.get() self.assertEqual(task.slug, run.task_slug) @@ -2405,7 +2533,7 @@ class TestProcesses(FixtureAPITestCase): self.assertFalse(process.tasks.exists()) self.client.force_login(self.user) - with self.assertNumQueries(18): + with self.assertNumQueries(20): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process.id)}) ) @@ -2422,24 +2550,59 @@ class TestProcesses(FixtureAPITestCase): """ A user can specify a ponos farm to use for a process """ - # Run get_default_farm_id once to ensure the new farm is not the default one - get_default_farm_id() - barley_farm = Farm.objects.create(name='Barley farm') - self.assertNotEqual(get_default_farm_id(), barley_farm.id) workers_process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) workers_process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) self.client.force_login(self.user) - with self.assertNumQueries(19): + with self.assertNumQueries(21): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(workers_process.id)}), - {'farm': str(barley_farm.id)} + {'farm': str(self.other_farm.id)} ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) workers_process.refresh_from_db() self.assertEqual(workers_process.state, State.Unscheduled) - self.assertEqual(workers_process.farm_id, barley_farm.id) + self.assertEqual(workers_process.farm_id, self.other_farm.id) + + def test_start_process_default_farm_guest(self): + workers_process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + workers_process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) + self.client.force_login(self.user) + self.default_farm.memberships.filter(user=self.user).delete() + + with self.assertNumQueries(10): + response = self.client.post( + reverse('api:process-start', kwargs={'pk': str(workers_process.id)}) + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertDictEqual(response.json(), { + 'farm': ['You do not have access to this farm.'], + }) + workers_process.refresh_from_db() + self.assertEqual(workers_process.state, State.Unscheduled) + self.assertIsNone(workers_process.farm) + + def test_start_process_farm_guest(self): + workers_process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + workers_process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) + self.client.force_login(self.user) + self.other_farm.memberships.filter(user=self.user).delete() + + with self.assertNumQueries(10): + response = self.client.post( + reverse('api:process-start', kwargs={'pk': str(workers_process.id)}), + {'farm': str(self.other_farm.id)} + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertDictEqual(response.json(), { + 'farm': ['You do not have access to this farm.'], + }) + workers_process.refresh_from_db() + self.assertEqual(workers_process.state, State.Unscheduled) + self.assertIsNone(workers_process.farm) def test_start_process_wrong_farm_id(self): """ @@ -2468,7 +2631,7 @@ class TestProcesses(FixtureAPITestCase): ({'thumbnails': 'gloubiboulga'}, {'thumbnails': ['Must be a valid boolean.']}) ] for (params, check) in wrong_params_checks: - with self.subTest(**params), self.assertNumQueries(8): + with self.subTest(**params), self.assertNumQueries(11): response = self.client.post(reverse('api:process-start', kwargs={'pk': str(process.id)}), params) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), check) @@ -2482,7 +2645,7 @@ class TestProcesses(FixtureAPITestCase): process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) self.client.force_login(self.user) - with self.assertNumQueries(8): + with self.assertNumQueries(11): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process.id)}), {'chunks': 43}, @@ -2554,7 +2717,7 @@ class TestProcesses(FixtureAPITestCase): element_type=self.corpus.types.get(slug='page') ) self.client.force_login(self.user) - with self.assertNumQueries(7): + with self.assertNumQueries(10): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process.id)}), {'use_cache': 'true', 'worker_activity': 'true', 'use_gpu': 'true'} @@ -2599,7 +2762,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) - with self.assertNumQueries(17): + with self.assertNumQueries(19): response = self.client.post( reverse('api:process-start', kwargs={'pk': str(process.id)}), {'use_cache': 'true', 'worker_activity': 'true', 'use_gpu': 'true'} @@ -2635,7 +2798,7 @@ class TestProcesses(FixtureAPITestCase): self.assertNotEqual(run_1.task_slug, run_2.task_slug) self.client.force_login(self.user) - with self.assertNumQueries(20): + with self.assertNumQueries(22): response = self.client.post(reverse('api:process-start', kwargs={'pk': str(process.id)})) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -2657,6 +2820,7 @@ class TestProcesses(FixtureAPITestCase): process = self.corpus.processes.create( creator=self.user, mode=ProcessMode.Files, + farm=self.default_farm, ) process.files.create(name='A PDF file', size=10e6, content_type='pdf', corpus=self.corpus) token_mock.return_value = '12345' @@ -2689,6 +2853,7 @@ class TestProcesses(FixtureAPITestCase): process = self.corpus.processes.create( creator=self.user, mode=ProcessMode.Workers, + farm=self.default_farm, ) token_mock.side_effect = [b'12345', b'78945'] run = process.worker_runs.create(version=self.version_with_model, parents=[], configuration=None) @@ -2797,7 +2962,8 @@ class TestProcesses(FixtureAPITestCase): process = self.corpus.processes.create( creator=self.user, mode=ProcessMode.Workers, - element_type=self.corpus.types.get(slug='page') + element_type=self.corpus.types.get(slug='page'), + farm=self.default_farm, ) process.worker_runs.create(version=self.dla, parents=[], configuration=None) process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) @@ -2819,7 +2985,8 @@ class TestProcesses(FixtureAPITestCase): process = self.corpus.processes.create( creator=self.user, mode=ProcessMode.Workers, - element_type=self.corpus.types.get(slug='page') + element_type=self.corpus.types.get(slug='page'), + farm=self.default_farm, ) process.worker_runs.create(version=self.dla, parents=[], configuration=None) process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) @@ -2837,7 +3004,8 @@ class TestProcesses(FixtureAPITestCase): process = self.corpus.processes.create( creator=self.user, mode=ProcessMode.Workers, - element_type=self.corpus.types.get(slug='page') + element_type=self.corpus.types.get(slug='page'), + farm=self.default_farm, ) process.worker_runs.create(version=self.dla, parents=[], configuration=None) process.worker_runs.create(version=self.recognizer, parents=[], configuration=None) @@ -2858,7 +3026,7 @@ class TestProcesses(FixtureAPITestCase): self.client.force_login(self.user) methods = (self.client.get, self.client.put, self.client.delete) for method in methods: - response = self.client.delete(reverse('api:process-select-failures', kwargs={'pk': str(self.elts_process.id)})) + response = method(reverse('api:process-select-failures', kwargs={'pk': str(self.elts_process.id)})) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) def test_select_failed_elts_requires_login(self): diff --git a/arkindex/process/tests/test_repos.py b/arkindex/process/tests/test_repos.py index a14df7dc54bc80bb2b3ac69467fc01e20f3c33be..75b3b6ceb109f66a35630348953aabb178aee914 100644 --- a/arkindex/process/tests/test_repos.py +++ b/arkindex/process/tests/test_repos.py @@ -5,7 +5,7 @@ from rest_framework import status from rest_framework.exceptions import ValidationError from rest_framework.serializers import DateTimeField -from arkindex.ponos.models import State, Task +from arkindex.ponos.models import Farm, State, Task from arkindex.process.models import ActivityState, Process, ProcessMode, Repository from arkindex.project.tests import FixtureTestCase from arkindex.users.models import Role, User @@ -29,6 +29,7 @@ class TestRepositories(FixtureTestCase): 'hash': cls.rev.hash, 'message': cls.rev.message } + cls.farm = Farm.objects.get() def build_repository_response(self): return [ @@ -74,14 +75,15 @@ class TestRepositories(FixtureTestCase): self.repo.save() task_count = Task.objects.count() - di = Process.objects.create( + process = Process.objects.create( mode=ProcessMode.Repository, revision=self.rev, creator=self.superuser, + farm=Farm.objects.first(), ) with self.assertRaisesMessage(ValidationError, 'Git repository does not have any valid credentials'): - di.run() + process.run() self.assertEqual(Task.objects.count(), task_count) @@ -167,7 +169,11 @@ class TestRepositories(FixtureTestCase): """ A Ponos task can retrieve the clone URL for the repository it should work on """ - process = self.repo_2.revisions.first().processes.create(mode=ProcessMode.Repository, creator=self.user) + process = self.repo_2.revisions.first().processes.create( + mode=ProcessMode.Repository, + creator=self.user, + farm=Farm.objects.first(), + ) process.run() task = process.tasks.get() self.repo_2.memberships.create(user=self.user, level=Role.Guest.value) @@ -192,7 +198,11 @@ class TestRepositories(FixtureTestCase): """ A Ponos task cannot retrieve the clone URL of a repository it should not work on """ - process = self.repo.revisions.first().processes.create(mode=ProcessMode.Repository, creator=self.user) + process = self.repo.revisions.first().processes.create( + mode=ProcessMode.Repository, + creator=self.user, + farm=Farm.objects.first(), + ) process.run() task = process.tasks.get() self.repo_2.memberships.create(user=self.user, level=Role.Guest.value) @@ -336,9 +346,11 @@ class TestRepositories(FixtureTestCase): git_provider_mock().get_or_create_revision.return_value = self.rev, None repo = Repository.objects.create(url='http://somewhere.com/repo') git_provider_mock().create_repo.return_value = repo - with self.assertNumQueries(11): + + with self.assertNumQueries(15): response = self.client.post(reverse('api:available-repositories', kwargs={'pk': self.creds.id}), {'id': 1111}) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + process = Process.objects.get(revision=self.rev) self.assertEqual(response.json(), { 'name': 'Import 1337 from http://my_repo.fake/workers/worker', @@ -369,7 +381,25 @@ class TestRepositories(FixtureTestCase): _, kwargs = process_run_mock.call_args self.assertDictEqual(kwargs, {}) self.assertIsNone(process.corpus) + self.assertEqual(process.farm, self.farm) # User is granted an admin access to the repository repo_right = repo.memberships.get(user=self.user) self.assertTrue(repo_right.level >= Role.Admin.value) + + @patch('arkindex.users.models.OAuthCredentials.git_provider_class') + def test_add_repository_farm_guest(self, git_provider_mock): + self.client.force_login(self.user) + git_provider_mock().get_or_create_revision.return_value = self.rev, None + repo = Repository.objects.create(url='http://somewhere.com/repo') + git_provider_mock().create_repo.return_value = repo + self.farm.memberships.filter(user=self.user).delete() + + with self.assertNumQueries(12): + response = self.client.post(reverse('api:available-repositories', kwargs={'pk': self.creds.id}), {'id': 1111}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertListEqual(response.json(), [ + 'The owner of the OAuth credentials does not have access to the default farm.', + ]) + self.assertFalse(self.rev.processes.exists()) diff --git a/arkindex/process/tests/test_signals.py b/arkindex/process/tests/test_signals.py index 8ca97b5736efdbe29bf5636768a7e1e775d5485d..569bcfe6d4325a0a6a596e4136f49cdfc40727c5 100644 --- a/arkindex/process/tests/test_signals.py +++ b/arkindex/process/tests/test_signals.py @@ -4,7 +4,7 @@ from django.urls import reverse from rest_framework import status from rest_framework.exceptions import ValidationError -from arkindex.ponos.models import State +from arkindex.ponos.models import Farm, State from arkindex.process.models import ( ActivityState, ProcessMode, @@ -51,12 +51,21 @@ class TestSignals(FixtureAPITestCase): configuration={"test": "test2"} ) - cls.process_1 = cls.corpus.processes.create(creator=cls.user, mode=ProcessMode.Workers) + cls.farm = Farm.objects.first() + cls.process_1 = cls.corpus.processes.create( + creator=cls.user, + mode=ProcessMode.Workers, + farm=cls.farm, + ) cls.run_1 = cls.process_1.worker_runs.create( version=cls.version_1, parents=[], ) - cls.process_2 = cls.corpus.processes.create(creator=cls.user, mode=ProcessMode.Workers) + cls.process_2 = cls.corpus.processes.create( + creator=cls.user, + mode=ProcessMode.Workers, + farm=cls.farm, + ) def test_worker_run_check_parents_recursive(self): run_2 = self.process_1.worker_runs.create( diff --git a/arkindex/process/tests/test_utils.py b/arkindex/process/tests/test_utils.py index baa86178c4c7155e4d60b592c18bf72ecab4454a..e0b39afd63acf8e969dd13196f9b95f81e900e20 100644 --- a/arkindex/process/tests/test_utils.py +++ b/arkindex/process/tests/test_utils.py @@ -3,7 +3,7 @@ import uuid from django.test import TestCase, override_settings from arkindex.ponos.models import Farm -from arkindex.process.utils import get_default_farm_id +from arkindex.process.utils import get_default_farm DEFAULT_FARM_ID = str(uuid.uuid4()) @@ -23,7 +23,7 @@ class TestProcessUtils(TestCase): super().setUp() # Force clean the module default farm cached global variable from arkindex.process import utils - setattr(utils, '__default_farm_id', None) + setattr(utils, '__default_farm', None) @override_settings(PONOS_DEFAULT_FARM=DEFAULT_FARM_ID) def test_config_ponos_farm_setting(self): @@ -31,19 +31,24 @@ class TestProcessUtils(TestCase): Default farm may be defined in settings """ with self.assertNumQueries(1): - self.assertEqual(str(get_default_farm_id()), str(self.corn_farm.id)) + self.assertEqual(str(get_default_farm()), str(self.corn_farm)) def test_default_ponos_farm(self): """ In case no default farm is defined, a random farm is selected as default. Subsequent calls does not hit the database """ - farm_id = None + self.assertEqual(Farm.objects.count(), 2) + with self.assertNumQueries(1): - farm_id = get_default_farm_id() - self.assertIn(farm_id, Farm.objects.values_list('id', flat=True)) + farm = get_default_farm() + self.assertIsInstance(farm, Farm) + + # No new farm was created + self.assertEqual(Farm.objects.count(), 2) + with self.assertNumQueries(0): - self.assertEqual(get_default_farm_id(), farm_id) + self.assertEqual(get_default_farm(), farm) def test_default_farm_no_existing_farm(self): """ @@ -51,10 +56,12 @@ class TestProcessUtils(TestCase): """ Farm.objects.all().delete() self.assertEqual(Farm.objects.count(), 0) - farm_id = None + with self.assertNumQueries(2): - farm_id = get_default_farm_id() - self.assertIsInstance(farm_id, uuid.UUID) - self.assertTrue(Farm.objects.filter(id=farm_id).exists()) + farm = get_default_farm() + self.assertIsInstance(farm, Farm) + + self.assertTrue(Farm.objects.filter(id=farm.id).exists()) + with self.assertNumQueries(0): - self.assertEqual(get_default_farm_id(), farm_id) + self.assertEqual(get_default_farm(), farm) diff --git a/arkindex/process/tests/test_workeractivity.py b/arkindex/process/tests/test_workeractivity.py index 1254fbcd12bbc5bbfa495d24b9e8200daf9ea87c..6763393577fb6959d2cff91c31a1bf7d7b350199 100644 --- a/arkindex/process/tests/test_workeractivity.py +++ b/arkindex/process/tests/test_workeractivity.py @@ -7,6 +7,7 @@ from django.urls import reverse from rest_framework import status from arkindex.documents.models import Corpus, Element +from arkindex.ponos.models import Farm from arkindex.process.models import ( ActivityState, Process, @@ -36,6 +37,7 @@ class TestWorkerActivity(FixtureTestCase): mode=ProcessMode.Workers, creator=cls.user, corpus=cls.corpus, + farm=Farm.objects.first(), ) cls.configuration = WorkerConfiguration.objects.create(worker=cls.worker_version.worker, name='A config', configuration={'a': 'b'}) cls.model = Model.objects.create(name='Mochi', public=False) @@ -329,8 +331,12 @@ class TestWorkerActivity(FixtureTestCase): in the serializer before the UPDATE query that normally updates any WorkerActivity if it exists. """ rev = Revision.objects.first() - process2 = Process.objects.create(mode=ProcessMode.Repository, creator=self.user, revision=rev) - process2.save() + process2 = Process.objects.create( + mode=ProcessMode.Repository, + creator=self.user, + revision=rev, + farm=Farm.objects.first(), + ) process2.run() task = process2.tasks.first() diff --git a/arkindex/process/tests/test_workerruns.py b/arkindex/process/tests/test_workerruns.py index b42482d1d91edafb8770399e6408588d1749a5cc..3b14e3405f7848e30ed26cb697e7af4556526275 100644 --- a/arkindex/process/tests/test_workerruns.py +++ b/arkindex/process/tests/test_workerruns.py @@ -36,7 +36,12 @@ class TestWorkerRuns(FixtureAPITestCase): super().setUpTestData() cls.artifact = Artifact.objects.first() cls.local_process = cls.user.processes.get(mode=ProcessMode.Local) - cls.process_1 = cls.corpus.processes.create(creator=cls.user, mode=ProcessMode.Workers) + cls.farm = Farm.objects.first() + cls.process_1 = cls.corpus.processes.create( + creator=cls.user, + mode=ProcessMode.Workers, + farm=cls.farm, + ) cls.version_1 = WorkerVersion.objects.get(worker__slug='reco') cls.worker_1 = cls.version_1.worker cls.worker_custom = Worker.objects.get(slug='custom') @@ -86,7 +91,7 @@ class TestWorkerRuns(FixtureAPITestCase): ) cls.agent = Agent.objects.create( - farm=Farm.objects.first(), + farm=cls.farm, hostname="claude", cpu_cores=42, cpu_frequency=1e15, @@ -332,6 +337,7 @@ class TestWorkerRuns(FixtureAPITestCase): process = self.corpus.processes.create( creator=self.user, mode=ProcessMode.Workers, + farm=self.farm, ) process.run() diff --git a/arkindex/process/tests/test_workers.py b/arkindex/process/tests/test_workers.py index c6a8b2dbb7ad198c231261fb17b42e5873a6a448..9d318af89d20ab490c35fa595d741a68cad74b06 100644 --- a/arkindex/process/tests/test_workers.py +++ b/arkindex/process/tests/test_workers.py @@ -3,6 +3,7 @@ import uuid from django.urls import reverse from rest_framework import status +from arkindex.ponos.models import Farm from arkindex.process.models import ( CorpusWorkerVersion, GitRefType, @@ -64,7 +65,12 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): size=1337, ) - process = cls.rev.processes.create(mode=ProcessMode.Repository, creator=cls.user) + cls.farm = Farm.objects.first() + process = cls.rev.processes.create( + mode=ProcessMode.Repository, + creator=cls.user, + farm=cls.farm, + ) process.run() cls.task = process.tasks.get() @@ -1542,7 +1548,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): self.version_1.docker_image = None self.version_1.save() - process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.farm, + ) process.run() task = process.tasks.first() @@ -1591,7 +1601,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): }) def test_update_version_available_docker_image_not_null(self): - process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.farm, + ) process.run() task = process.tasks.first() self.version_1.docker_image = task.artifacts.create( @@ -1616,7 +1630,11 @@ class TestWorkersWorkerVersions(FixtureAPITestCase): }) def test_update_version_available_docker_image_iid_not_null(self): - process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) + process = self.corpus.processes.create( + creator=self.user, + mode=ProcessMode.Workers, + farm=self.farm, + ) process.run() task = process.tasks.first() self.version_1.docker_image = task.artifacts.create( diff --git a/arkindex/process/utils.py b/arkindex/process/utils.py index d79ec264c26c606df6e33f0552e5ecda49a945ab..cf2a57af6d19622d5064634d8522205f2e4a841c 100644 --- a/arkindex/process/utils.py +++ b/arkindex/process/utils.py @@ -7,29 +7,28 @@ from django.db.models.functions import Cast, Concat, NullIf from arkindex.project.tools import RTrimChr -__default_farm_id = None +__default_farm = None -def get_default_farm_id(): +def get_default_farm(): """ - Return the default Ponos Farm ID. + Return the default Ponos Farm. If defined, use PONOS_DEFAULT_FARM setting. Otherwise, pick the first existing farm or create it. - This method uses a global variable to store the cached value retrieved in a idempotant way. - It could return an invalid farm ID in case the default farm is removed or updated during runtime. + This method uses a global variable to store the cached value retrieved in a idempotent way. + It could return an invalid farm in case the default farm is removed or updated during runtime. """ from arkindex.ponos.models import Farm - global __default_farm_id - if __default_farm_id is not None: - return __default_farm_id + global __default_farm + if __default_farm is not None: + return __default_farm if settings.PONOS_DEFAULT_FARM: - farm = Farm.objects.get(pk=settings.PONOS_DEFAULT_FARM) + __default_farm = Farm.objects.get(pk=settings.PONOS_DEFAULT_FARM) else: - farm = Farm.objects.first() or Farm.objects.create(name="Default farm") - __default_farm_id = farm.id - return __default_farm_id + __default_farm = Farm.objects.order_by("name").first() or Farm.objects.create(name="Default farm") + return __default_farm def hash_object(object): diff --git a/arkindex/project/tests/__init__.py b/arkindex/project/tests/__init__.py index f8cab52410dbade060bcc0325971b50c53f5dcb8..a361b21270b79ea15f533dd3b208a9fec87ef07e 100644 --- a/arkindex/project/tests/__init__.py +++ b/arkindex/project/tests/__init__.py @@ -124,18 +124,15 @@ class FixtureMixin(object): cls.group = Group.objects.get(name='User group') cls.superuser = User.objects.get(email='root@root.fr') cls.imgsrv = ImageServer.objects.get(url='http://server') - # Clear content type cache after setting up test data in case a single test is run (see tearDown method above) - ContentType.objects.clear_cache() - def setUp(self): - super().setUp() + def clear_caches(self): # Clean content type cache for SQL requests checks consistency ContentType.objects.clear_cache() # Force clean the default farm global variable in `arkindex.process.utils` module # This is required not to alter query counts and avoid caching a farm that does not exist in the fixture from arkindex.process import utils - setattr(utils, '__default_farm_id', None) + setattr(utils, '__default_farm', None) # Clear the local cached properties so that it is re-fetched on each test # to avoid intermittently changing query counts. @@ -154,10 +151,13 @@ class FixtureMixin(object): except AttributeError: pass + def setUp(self): + super().setUp() + self.clear_caches() + @contextmanager def subTest(self, *args, **kwargs): - # Clear content type cache for consistent SQL queries during sub-tests - ContentType.objects.clear_cache() + self.clear_caches() with super().subTest(*args, **kwargs): yield diff --git a/arkindex/users/models.py b/arkindex/users/models.py index 3a92035320a71ac000bc1120f34c265f12fd1f82..3764f14854abf7be672a32b30d39380702c30d51 100644 --- a/arkindex/users/models.py +++ b/arkindex/users/models.py @@ -182,6 +182,9 @@ class Group(models.Model): level=level, ) + def __str__(self): + return self.name + class OAuthStatus(Enum): Created = 'created' diff --git a/arkindex/users/tests/test_generic_memberships.py b/arkindex/users/tests/test_generic_memberships.py index e8da6a8a9c5970134d8f20e6d6f5079b2383322c..172789d620bb3edca8ddd5a17362aeb5adca7ad6 100644 --- a/arkindex/users/tests/test_generic_memberships.py +++ b/arkindex/users/tests/test_generic_memberships.py @@ -146,7 +146,7 @@ class TestMembership(FixtureAPITestCase): response = self.client.get(reverse('api:memberships-list'), wrong_params) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { - '__all__': ['Exactly one of those query parameters must be defined: corpus, repository, group, worker, model.'] + '__all__': ['Exactly one of those query parameters must be defined: corpus, repository, group, worker, model, farm.'] }) def test_list_members_public_content(self): diff --git a/arkindex/users/utils.py b/arkindex/users/utils.py index 20272beed63218b08bd7a50b79b801fbeef1da57..9d0a68080f5c7d1173eaa74f2a4595bbc86cf55b 100644 --- a/arkindex/users/utils.py +++ b/arkindex/users/utils.py @@ -3,6 +3,7 @@ from django.db.models.query_utils import DeferredAttribute from enumfields import Enum from arkindex.documents.models import Corpus +from arkindex.ponos.models import Farm from arkindex.process.models import Repository, Worker from arkindex.training.models import Model from arkindex.users.models import Group, Role @@ -16,6 +17,7 @@ class RightContent(Enum): group = Group worker = Worker model = Model + farm = Farm def has_public_field(model):