Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • arkindex/backend
1 result
Show changes
Commits on Source (3)
......@@ -51,6 +51,13 @@ release:
git tag $(version)
git push origin master $(version)
clean-docker:
$(eval containers:=$(shell docker ps -a -q))
@if [ -n "$(containers)" ]; then \
echo "Cleaning up past containers\n" \
docker rm -f $(containers) ; \
fi
stack: docker/ssl/ark-cert.pem
docker compose -p arkindex up --build
......
......@@ -111,6 +111,7 @@ At the root of the repository is a Makefile that provides commands for common op
* `make` or `make all`: Clean and build;
* `make base`: Create and push the `arkindex-base` Docker image that is used to build the `arkindex-app` image;
* `make clean`: Cleanup the Python package build and cache files;
* `make clean-docker`: Deletes all running containers to avoid naming and network ports conflicts;
* `make build`: Build the arkindex Python package and recreate the `arkindex-app:latest` without pushing to the GitLab container registry;
* `make test-fixtures`: Create the unit tests fixtures on a temporary PostgreSQL database and save them to the `data.json` file used by most Django unit tests.
......@@ -205,3 +206,7 @@ docker volume create arkindex_miniodata
mv /usr/share/arkindex/s3/data/iiif /var/lib/docker/volumes/arkindex_miniodata/_data/uploads
mv /usr/share/arkindex/s3/data/{export,iiif-cache,ponos-logs,ponos-artifacts,staging,thumbnails,training} /var/lib/docker/volumes/arkindex_miniodata/_data/
```
You will also need to setup [mkcert](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation) as we do not use Teklia development Certificate Authority anymore. `mkcert` will take care of SSL certificates automatically, updating your browsers and system certificate store !
Finally, you can remove the `architecture` project from your work folder, as it's now archived and could be confusing.
1.6.0-beta1
1.6.0-beta2
......@@ -67,7 +67,7 @@ from arkindex.documents.serializers.elements import (
ElementNeighborsSerializer,
ElementParentSerializer,
ElementSerializer,
ElementSlimSerializer,
ElementTinySerializer,
ElementTypeSerializer,
MetaDataBulkSerializer,
MetaDataCreateSerializer,
......@@ -1410,7 +1410,7 @@ class ManageSelection(SelectionMixin, ListAPIView):
@extend_schema(
operation_id="AddSelection",
description="Add specific elements",
responses={201: ElementSlimSerializer},
responses={201: ElementTinySerializer},
request=inline_serializer(
name="AddSelectionBodySerializer",
fields={"ids": serializers.ListField(child=serializers.UUIDField())}
......@@ -1450,7 +1450,7 @@ class ManageSelection(SelectionMixin, ListAPIView):
prefetch_related_objects(elements, "corpus", "image__server", "type")
return Response(
status=status.HTTP_201_CREATED,
data=ElementSlimSerializer(
data=ElementTinySerializer(
elements,
context={"request": request},
many=True
......
......@@ -21,7 +21,7 @@ from arkindex.documents.models import (
Transcription,
TranscriptionEntity,
)
from arkindex.documents.serializers.elements import ElementSlimSerializer
from arkindex.documents.serializers.elements import ElementTinySerializer
from arkindex.documents.serializers.entities import (
BaseEntitySerializer,
CreateEntityRoleErrorResponseSerializer,
......@@ -218,7 +218,7 @@ class EntityElements(ListAPIView):
"""
Get all elements that have a link with the entity
"""
serializer_class = ElementSlimSerializer
serializer_class = ElementTinySerializer
# For OpenAPI type discovery: an entity's ID is in the path
queryset = Entity.objects.none()
......
import math
import uuid
from collections import defaultdict
from functools import cached_property
from textwrap import dedent
from django.conf import settings
......@@ -23,7 +24,6 @@ from arkindex.documents.serializers.light import (
from arkindex.documents.serializers.ml import ClassificationSerializer, WorkerRunSummarySerializer
from arkindex.images.models import Image
from arkindex.images.serializers import ZoneSerializer
from arkindex.ponos.utils import get_process_from_task_auth
from arkindex.process.models import WorkerVersion
from arkindex.project.fields import Array
from arkindex.project.mixins import SelectionMixin
......@@ -429,29 +429,6 @@ class ElementTinySerializer(serializers.ModelSerializer):
)
class ElementSlimSerializer(ElementTinySerializer):
"""
Fully serialises a document
"""
thumbnail_put_url = serializers.SerializerMethodField(read_only=True)
@extend_schema_field(serializers.CharField(allow_null=True))
def get_thumbnail_put_url(self, element):
"""
Only set the Thumbnail PUT URL for Ponos tasks that
are running the thumbnails generation on a folder.
"""
if element.type.folder:
process = get_process_from_task_auth(self.context["request"])
if process and process.generate_thumbnails:
return element.thumbnail.s3_put_url
class Meta(ElementTinySerializer.Meta):
model = Element
fields = ElementTinySerializer.Meta.fields + ("thumbnail_put_url",)
read_only_fields = ElementTinySerializer.Meta.read_only_fields + ("thumbnail_put_url",)
@extend_schema_serializer(deprecate_fields=("worker_version_id", ))
class ElementListSerializer(ElementTinySerializer):
created = serializers.DateTimeField(read_only=True)
......@@ -555,7 +532,7 @@ class ElementParentSerializer(serializers.Serializer):
@extend_schema_serializer(deprecate_fields=("worker_version", ))
class ElementSerializer(ElementSlimSerializer):
class ElementSerializer(ElementTinySerializer):
"""
Serialize an element with its metadata and classifications
"""
......@@ -591,9 +568,11 @@ class ElementSerializer(ElementSlimSerializer):
worker_run = WorkerRunSummarySerializer(read_only=True, allow_null=True)
thumbnail_put_url = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Element
fields = ElementSlimSerializer.Meta.fields + (
fields = ElementTinySerializer.Meta.fields + (
"created",
"creator",
"rights",
......@@ -603,32 +582,57 @@ class ElementSerializer(ElementSlimSerializer):
"polygon",
"worker_version",
"confidence",
"worker_run"
"worker_run",
"thumbnail_put_url",
)
read_only_fields = ElementSlimSerializer.Meta.read_only_fields + (
read_only_fields = ElementTinySerializer.Meta.read_only_fields + (
"created",
"creator",
"rights",
"metadata_count",
"classifications",
"worker_version",
"worker_run"
"worker_run",
"thumbnail_put_url",
)
@extend_schema_field(serializers.ListField(child=serializers.ChoiceField(["read", "write", "admin"])))
def get_rights(self, element):
@cached_property
def element_rights(self):
if not self.instance:
return
user = self.context["request"].user
level = get_max_level(user, element.corpus)
level = get_max_level(user, self.instance.corpus)
# Admin access is granted to both corpus admins and element creators that are corpus contributors
if level >= Role.Contributor.value and self.instance.creator_id == user.id:
return Role.Admin.value
return level
@extend_schema_field(serializers.ListField(child=serializers.ChoiceField(["read", "write", "admin"])))
def get_rights(self, element):
rights = ["read"]
if level >= Role.Contributor.value:
if self.element_rights >= Role.Contributor.value:
rights.append("write")
# Admin access is granted to both corpus admins and element creators
if level >= Role.Admin.value or (level >= Role.Contributor.value and element.creator_id == user.id):
if self.element_rights >= Role.Admin.value:
rights.append("admin")
return rights
@extend_schema_field(serializers.CharField(
allow_null=True,
help_text=dedent("""
URL where a PUT request may be sent to upload a new thumbnail for this element.
Only available on folder elements.
Requires **admin** access to the corpus, or **contributor** access to the corpus and to be the element's creator.
"""),
))
def get_thumbnail_put_url(self, element):
if element.type.folder and self.element_rights >= Role.Admin.value:
return element.thumbnail.s3_put_url
def update(self, instance, validated_data):
image = validated_data.pop("image", None)
polygon = validated_data.pop("polygon", None)
......
......@@ -92,8 +92,8 @@ class TestCreateElements(FixtureAPITestCase):
"id": str(volume.id),
"type": volume.type.slug,
"name": volume.name,
"thumbnail_put_url": None,
"thumbnail_url": volume.thumbnail.s3_url,
"thumbnail_put_url": volume.thumbnail.s3_put_url,
"worker_version": None,
"confidence": None,
"creator": "Test user",
......
......@@ -161,7 +161,6 @@ class TestEntitiesAPI(FixtureAPITestCase):
"public": self.corpus.public
},
"thumbnail_url": None,
"thumbnail_put_url": None,
"zone": {
"id": str(e.id),
"url": e.iiif_url,
......
......@@ -43,7 +43,7 @@ class TestRetrieveElements(FixtureAPITestCase):
"public": True,
},
"thumbnail_url": self.vol.thumbnail.s3_url,
"thumbnail_put_url": None,
"thumbnail_put_url": self.vol.thumbnail.s3_put_url,
"worker_version": None,
"confidence": None,
"zone": None,
......@@ -106,7 +106,7 @@ class TestRetrieveElements(FixtureAPITestCase):
response = self.client.get(reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)}))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["thumbnail_url"], self.vol.thumbnail.s3_url)
self.assertIsNone(response.json()["thumbnail_put_url"])
self.assertEqual(response.json()["thumbnail_put_url"], self.vol.thumbnail.s3_put_url)
self.assertFalse(self.page.type.folder)
response = self.client.get(reverse("api:element-retrieve", kwargs={"pk": str(self.page.id)}))
......@@ -114,6 +114,38 @@ class TestRetrieveElements(FixtureAPITestCase):
self.assertIsNone(response.json()["thumbnail_url"])
self.assertIsNone(response.json()["thumbnail_put_url"])
@patch("arkindex.documents.serializers.elements.get_max_level")
def test_get_element_thumbnail_put_acl(self, get_max_level_mock):
"""
RetrieveElement returns a thumbnail_put_url for corpus admins,
or for corpus contributors that have created the element
"""
self.client.force_login(self.user)
cases = [
(Role.Guest, self.superuser, False),
(Role.Guest, self.user, False),
(Role.Contributor, self.superuser, False),
(Role.Contributor, self.user, True),
(Role.Admin, self.superuser, True),
(Role.Admin, self.user, True),
]
for role, creator, has_put_url in cases:
with self.subTest(role=role):
get_max_level_mock.return_value = role.value
self.corpus.memberships.filter(user=self.user).update(level=role.value)
self.vol.creator = creator
self.vol.save()
with self.assertNumQueries(4):
response = self.client.get(reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)}))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
response.json()["thumbnail_put_url"],
self.vol.thumbnail.s3_put_url if has_put_url else None
)
@override_settings(ARKINDEX_TASKS_IMAGE="task_image")
def test_get_element_thumbnail_put_ponos_task(self):
"""
......@@ -148,27 +180,6 @@ class TestRetrieveElements(FixtureAPITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIsNone(response.json()["thumbnail_put_url"])
def test_get_element_thumbnail_put_requires_thumbnails_task(self):
"""
Only tasks that are intended to generate thumbnails (ARKINDEX_TASKS_IMAGE + thumbnails_generation command)
can retrieve the thumbnails PUT URL.
"""
process = Process.objects.create(
mode=ProcessMode.Workers,
creator=self.user,
farm=Farm.objects.first(),
corpus=self.corpus
)
process.run()
task = process.tasks.first()
self.assertTrue(self.vol.type.folder)
response = self.client.get(
reverse("api:element-retrieve", kwargs={"pk": str(self.vol.id)}),
HTTP_AUTHORIZATION=f"Ponos {task.token}",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIsNone(response.json()["thumbnail_put_url"])
def test_get_element_creator(self):
self.vol.creator = self.user
self.vol.save()
......@@ -220,7 +231,7 @@ class TestRetrieveElements(FixtureAPITestCase):
"public": True,
},
"thumbnail_url": self.vol.thumbnail.s3_url,
"thumbnail_put_url": None,
"thumbnail_put_url": self.vol.thumbnail.s3_put_url,
"worker_version": str(self.worker_version.id),
"confidence": None,
"zone": None,
......@@ -255,7 +266,7 @@ class TestRetrieveElements(FixtureAPITestCase):
"public": True,
},
"thumbnail_url": self.vol.thumbnail.s3_url,
"thumbnail_put_url": None,
"thumbnail_put_url": self.vol.thumbnail.s3_put_url,
"worker_version": None,
"confidence": None,
"zone": None,
......
......@@ -7,7 +7,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from arkindex.documents.models import Corpus, Element
from arkindex.documents.serializers.elements import ElementSlimSerializer
from arkindex.documents.serializers.elements import ElementTinySerializer
from arkindex.images.models import Image
from arkindex.images.serializers import (
CreateImageErrorResponseSerializer,
......@@ -151,7 +151,7 @@ class ImageElements(ListAPIView):
# For OpenAPI type discovery: an image's ID is in the path
queryset = Image.objects.none()
permission_classes = (IsVerified, )
serializer_class = ElementSlimSerializer
serializer_class = ElementTinySerializer
def get_queryset(self):
filters = {
......
......@@ -76,8 +76,8 @@ class ProcessSerializer(ProcessLightSerializer):
"""
Serialize a process with its settings
"""
from arkindex.documents.serializers.elements import ElementSlimSerializer
element = ElementSlimSerializer(read_only=True)
from arkindex.documents.serializers.elements import ElementTinySerializer
element = ElementTinySerializer(read_only=True)
element_id = serializers.PrimaryKeyRelatedField(
queryset=Element.objects.none(),
default=None,
......
......@@ -431,7 +431,6 @@ class TestCreateProcess(FixtureAPITestCase):
"rotation_angle": 0,
"mirrored": False,
"thumbnail_url": None,
"thumbnail_put_url": None,
},
"template_id": None,
"load_children": False,
......