Skip to content
Snippets Groups Projects
Commit dd4e3a5b authored by Tore.Brede's avatar Tore.Brede
Browse files

Merge branch 'GREG-91_expand_sponsor_api' into 'master'

GREG-91: Expand sponsor api

See merge request !126
parents cdb2a423 02e094f7
No related branches found
No related tags found
1 merge request!126GREG-91: Expand sponsor api
Pipeline #98927 passed
from rest_framework import serializers
from greg.api.serializers.organizational_unit import OrganizationalUnitSerializer
from greg.models import Sponsor
class SponsorSerializer(serializers.ModelSerializer):
units = OrganizationalUnitSerializer(many=True, read_only=True)
class Meta:
model = Sponsor
fields = ["id", "feide_id", "first_name", "last_name"]
fields = ["id", "feide_id", "first_name", "last_name", "units"]
......@@ -11,7 +11,11 @@ from greg.api.views.person import (
IdentityViewSet,
)
from greg.api.views.role_type import RoleTypeViewSet
from greg.api.views.sponsor import SponsorViewSet, SponsorGuestsViewSet
from greg.api.views.sponsor import (
SponsorViewSet,
SponsorGuestsViewSet,
SponsorOrgunitLinkView,
)
router = DefaultRouter(trailing_slash=False)
router.register(r"persons", PersonViewSet, basename="person")
......@@ -20,7 +24,6 @@ router.register(r"consenttypes", ConsentTypeViewSet, basename="consenttype")
router.register(r"sponsors", SponsorViewSet, basename="sponsor")
router.register(r"orgunit", OrganizationalUnitViewSet, basename="orgunit")
urlpatterns = router.urls
urlpatterns += [
......@@ -53,4 +56,9 @@ urlpatterns += [
SponsorGuestsViewSet.as_view({"get": "list"}),
name="sponsor_guests-list",
),
re_path(
r"^sponsors/(?P<sponsor_id>[0-9]+)/orgunit/(?P<orgunit_id>[0-9]+)$",
SponsorOrgunitLinkView.as_view({"post": "create", "delete": "destroy"}),
name="sponsor_orgunit-detail",
),
]
import logging
from django.db.models import ProtectedError
from django.core.exceptions import ValidationError
from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework import mixins
from rest_framework import mixins, status
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from greg.api.pagination import PrimaryKeyCursorPagination
from greg.api.serializers import PersonSerializer
from greg.api.serializers.organizational_unit import OrganizationalUnitSerializer
from greg.api.serializers.sponsor import SponsorSerializer
from greg.models import Sponsor, Person
from greg.models import Sponsor, Person, OrganizationalUnit
logger = logging.getLogger(__name__)
class SponsorViewSet(ModelViewSet):
......@@ -16,6 +24,18 @@ class SponsorViewSet(ModelViewSet):
pagination_class = PrimaryKeyCursorPagination
lookup_field = "id"
def destroy(self, request, *args, **kwargs):
"""Overridden method to handle exception"""
instance = self.get_object()
try:
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
except ProtectedError as error:
# Probably the sponsor is being used somewhere as a foreign key and cannot be deleted.
# Log the error and return bad request instead an internal server error
logger.error(error)
return Response(status=status.HTTP_400_BAD_REQUEST)
@extend_schema(
parameters=[
......@@ -41,3 +61,50 @@ class SponsorGuestsViewSet(mixins.ListModelMixin, GenericViewSet):
sponsor_id = self.kwargs["sponsor_id"]
qs = qs.filter(roles__sponsor_id=sponsor_id).order_by("id")
return qs
class SponsorOrgunitLinkView(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericViewSet,
):
"""Endpoint that allows manipulation of links between a sponsor and the units he is attached to"""
queryset = OrganizationalUnit.objects.all().order_by("id")
serializer_class = OrganizationalUnitSerializer
pagination_class = PrimaryKeyCursorPagination
# This is set so that the orgunit_id parameter in the path of the URL is used for looking up objects
lookup_url_kwarg = "orgunit_id"
def create(self, request, *args, **kwargs):
(sponsor_id, orgunit_id) = self._extract_sponsor_and_orgunit(kwargs)
# Default to false if hierarchical_access is not specified
hierarchical_access = request.data.get("hierarchical_access", "False")
sponsor = Sponsor.objects.get(id=sponsor_id)
sponsor.units.add(
orgunit_id, through_defaults={"hierarchical_access": hierarchical_access}
)
return Response(status=status.HTTP_204_NO_CONTENT)
def destroy(self, request, *args, **kwargs):
"""Overridden because a delete at this endpoint should not attempt to delete the organizational unit,
but the link between the sponsor and the unit"""
(sponsor_id, orgunit_id) = self._extract_sponsor_and_orgunit(kwargs)
sponsor = Sponsor.objects.filter(id=sponsor_id).get()
sponsor.units.remove(orgunit_id)
return Response(status=status.HTTP_204_NO_CONTENT)
def _extract_sponsor_and_orgunit(self, request_data):
sponsor_id = request_data["sponsor_id"]
orgunit_id = request_data["orgunit_id"]
if sponsor_id is None:
raise ValidationError("Missing sponsor ID")
if orgunit_id is None:
raise ValidationError("Orgunit ID")
return (sponsor_id, orgunit_id)
......@@ -3,6 +3,14 @@ from rest_framework import status
from rest_framework.reverse import reverse
from greg.models import (
OrganizationalUnit,
Sponsor,
Person,
Identity,
SponsorOrganizationalUnit,
)
@pytest.mark.django_db
def test_add_sponsor(client):
......@@ -42,3 +50,140 @@ def test_sponsor_empty_guest_list(client, sponsor_guy):
guests_for_sponsor = client.get(url).json()["results"]
assert len(guests_for_sponsor) == 0
@pytest.mark.django_db
def test_add_sponsor_with_unit(client, unit_foo: OrganizationalUnit):
sponsor_url = reverse("v1:sponsor-list")
data = {
"feide_id": "sponsor@example.org",
"first_name": "Test",
"last_name": "Sponsor",
}
response = client.post(sponsor_url, data=data)
sponsor_id = response.json()["id"]
assert response.status_code == status.HTTP_201_CREATED
sponsor_lookup_response = client.get(sponsor_url, kwargs={"id": sponsor_id})
sponsor_lookup_response_body = sponsor_lookup_response.json()
assert len(sponsor_lookup_response_body["results"]) == 1
assert len(sponsor_lookup_response_body["results"][0]["units"]) == 0
data = {"hierarchical_access": "True"}
create_sponsor_link_url = reverse(
"v1:sponsor_orgunit-detail",
kwargs={"sponsor_id": sponsor_id, "orgunit_id": unit_foo.id},
)
response = client.post(create_sponsor_link_url, data=data)
assert response.status_code == status.HTTP_204_NO_CONTENT
sponsor_lookup_response = client.get(sponsor_url, kwargs={"id": sponsor_id})
sponsor_lookup_response_body = sponsor_lookup_response.json()
assert len(sponsor_lookup_response_body["results"][0]["units"]) == 1
attached_unit = sponsor_lookup_response_body["results"][0]["units"][0]
assert attached_unit["id"] == unit_foo.id
@pytest.mark.django_db
def test_remove_sponsor_orgunit_link(
client, sponsor_guy: Sponsor, unit_foo: OrganizationalUnit
):
sponsor_detail_url = reverse("v1:sponsor-detail", kwargs={"id": sponsor_guy.id})
response_get = client.get(sponsor_detail_url).json()
assert len(response_get["units"]) == 0
data = {"hierarchical_access": "True"}
sponsor_orgunit_url = reverse(
"v1:sponsor_orgunit-detail",
kwargs={"sponsor_id": sponsor_guy.id, "orgunit_id": unit_foo.id},
)
response = client.post(sponsor_orgunit_url, data=data)
assert response.status_code == status.HTTP_204_NO_CONTENT
response_get = client.get(sponsor_detail_url).json()
assert len(response_get["units"]) == 1
assert response_get["units"][0]["id"] == unit_foo.id
response_delete = client.delete(sponsor_orgunit_url)
assert response_delete.status_code == status.HTTP_204_NO_CONTENT
response_get = client.get(sponsor_detail_url).json()
assert len(response_get["units"]) == 0
@pytest.mark.django_db
def test_delete_sponsor_connected_to_identity_not_allowed(
client,
sponsor_guy: Sponsor,
person_foo: Person,
person_foo_verified: Identity,
unit_foo: OrganizationalUnit,
):
sponsor_detail_url = reverse("v1:sponsor-detail", kwargs={"id": sponsor_guy.id})
response_get = client.get(sponsor_detail_url).json()
assert len(response_get["units"]) == 0
response_delete = client.delete(sponsor_detail_url)
# The delete should fail
assert response_delete.status_code == status.HTTP_400_BAD_REQUEST
response_get = client.get(sponsor_detail_url).json()
assert response_get["id"] == sponsor_guy.id
# Remove the identity and try to delete the sponsor again
url_delete = reverse(
"v1:person_identity-detail",
kwargs={"person_id": person_foo.id, "id": person_foo_verified.id},
)
client.delete(url_delete)
response_delete = client.delete(sponsor_detail_url)
assert response_delete.status_code == status.HTTP_204_NO_CONTENT
# Check that the sponsor has been deleted
response_get = client.get(sponsor_detail_url)
assert response_get.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.django_db
def test_add_sponsor_unit_link_with_no_access_parameter(
client, unit_foo: OrganizationalUnit
):
sponsor_url = reverse("v1:sponsor-list")
data = {
"feide_id": "sponsor@example.org",
"first_name": "Test",
"last_name": "Sponsor",
}
response = client.post(sponsor_url, data=data)
sponsor_id = response.json()["id"]
# Do a post with no data
create_sponsor_link_url = reverse(
"v1:sponsor_orgunit-detail",
kwargs={"sponsor_id": sponsor_id, "orgunit_id": unit_foo.id},
)
response = client.post(create_sponsor_link_url)
assert response.status_code == status.HTTP_204_NO_CONTENT
# Check that the unit is attached to the sponsor
sponsor_lookup_response = client.get(sponsor_url, kwargs={"id": sponsor_id})
sponsor_lookup_response_body = sponsor_lookup_response.json()
assert len(sponsor_lookup_response_body["results"][0]["units"]) == 1
attached_unit = sponsor_lookup_response_body["results"][0]["units"][0]
assert attached_unit["id"] == unit_foo.id
# Check that hierarchical_access is set to False for the link between the sponsor and unit
sponsor_organization_unit = SponsorOrganizationalUnit.objects.filter(
sponsor_id=sponsor_id, organizational_unit_id=unit_foo.id
).get()
assert not sponsor_organization_unit.hierarchical_access
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment