diff --git a/isic/core/api/collection.py b/isic/core/api/collection.py index 1b6ea9a4..21b9d6de 100644 --- a/isic/core/api/collection.py +++ b/isic/core/api/collection.py @@ -109,6 +109,35 @@ def collection_autocomplete( return collections[:20] +@router.get("/sharing-info/", response={200: list, 403: dict}, include_in_schema=False) +def collection_sharing_info(request, collection_ids: list[int] = Query(...)): + if not request.user.is_staff: + return 403, {"error": "You do not have permission to view sharing info."} + + collections = ( + Collection.objects.filter(id__in=collection_ids) + .select_related("creator") + .prefetch_related("shares") + ) + + def display_name(user): + return user.get_full_name() or user.email + + return [ + { + "id": collection.id, + "name": collection.name, + "public": collection.public, + "owner": { + "id": collection.creator.id, + "name": display_name(collection.creator), + }, + "shared_with": [{"id": u.id, "name": display_name(u)} for u in collection.shared_with], + } + for collection in collections + ] + + @router.get( "/{id}/", response=CollectionOut, diff --git a/isic/core/tests/test_api_collection.py b/isic/core/tests/test_api_collection.py index a4d42bf9..d00315ab 100644 --- a/isic/core/tests/test_api_collection.py +++ b/isic/core/tests/test_api_collection.py @@ -272,6 +272,50 @@ def test_core_api_collection_share_no_notify( assert len(mailoutbox) == 0 +@pytest.mark.django_db +@pytest.mark.parametrize( + ("client_", "expected_status"), + [ + (lf("client"), 403), + (lf("authenticated_client"), 403), + (lf("staff_client"), 200), + ], + ids=["anonymous", "authenticated", "staff"], +) +def test_core_api_collection_sharing_info_permissions(client_, expected_status, collection_factory): + collection = collection_factory(public=True) + r = client_.get( + reverse("api:collection_sharing_info"), + {"collection_ids": [collection.pk]}, + ) + assert r.status_code == expected_status + + +@pytest.mark.django_db +def test_core_api_collection_sharing_info(staff_client, collection_factory, user_factory): + owner = user_factory() + grantee = user_factory() + collection = collection_factory(creator=owner, public=False) + collection.shares.add(grantee, through_defaults={"grantor": owner}) + + r = staff_client.get( + reverse("api:collection_sharing_info"), + {"collection_ids": [collection.pk]}, + ) + + assert r.status_code == 200 + data = r.json() + assert len(data) == 1 + assert data[0]["id"] == collection.pk + assert data[0]["name"] == collection.name + assert data[0]["public"] is False + assert data[0]["owner"]["id"] == owner.pk + assert data[0]["owner"]["name"] == owner.get_full_name() or owner.email + assert len(data[0]["shared_with"]) == 1 + assert data[0]["shared_with"][0]["id"] == grantee.pk + assert data[0]["shared_with"][0]["name"] == grantee.get_full_name() or grantee.email + + @pytest.mark.django_db @pytest.mark.skip("TODO: fix this test") def test_core_api_collection_license_breakdown( diff --git a/isic/ingest/templates/ingest/cohort_publish.html b/isic/ingest/templates/ingest/cohort_publish.html index 5cd81a7a..32a7d0eb 100644 --- a/isic/ingest/templates/ingest/cohort_publish.html +++ b/isic/ingest/templates/ingest/cohort_publish.html @@ -60,6 +60,15 @@ +
+ Publishing to these collections will give access to: + +
+ @@ -107,6 +116,45 @@ return $container; } + + function sharingInfo() { + return { + entries: [], + + init() { + const select = document.getElementById("additional-collections-selection"); + select.addEventListener("change", () => { + this.fetchSharingInfo(); + }); + }, + + async fetchSharingInfo() { + const select = document.getElementById("additional-collections-selection"); + const selectedIds = Array.from(select.selectedOptions, opt => opt.value); + + if (selectedIds.length === 0) { + this.entries = []; + return; + } + + const params = new URLSearchParams(); + selectedIds.forEach(id => params.append("collection_ids", id)); + + const { data } = await axios.get( + "{% url 'api:collection_sharing_info' %}?" + params.toString() + ); + + const entries = []; + data.forEach(col => { + entries.push(col.owner.name + " (owner of " + col.name + ")"); + col.shared_with.forEach(user => { + entries.push(user.name + " (shared with " + col.name + ")"); + }); + }); + this.entries = entries; + }, + }; + }