diff --git a/coldfront/plugins/api/serializers.py b/coldfront/plugins/api/serializers.py index 6664696805..69ad36a63b 100644 --- a/coldfront/plugins/api/serializers.py +++ b/coldfront/plugins/api/serializers.py @@ -5,8 +5,8 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from coldfront.core.allocation.models import Allocation, AllocationChangeRequest -from coldfront.core.project.models import Project, ProjectUser +from coldfront.core.allocation.models import Allocation, AllocationAttribute, AllocationChangeRequest, AllocationUser +from coldfront.core.project.models import Project, ProjectAttribute, ProjectUser from coldfront.core.resource.models import Resource @@ -37,6 +37,8 @@ class AllocationSerializer(serializers.ModelSerializer): resource = serializers.ReadOnlyField(source="get_resources_as_string") project = serializers.SlugRelatedField(slug_field="title", read_only=True) status = serializers.SlugRelatedField(slug_field="name", read_only=True) + allocation_users = serializers.SerializerMethodField() + allocation_attributes = serializers.SerializerMethodField() class Meta: model = Allocation @@ -45,8 +47,39 @@ class Meta: "project", "resource", "status", + "allocation_users", + "allocation_attributes", ) + def get_allocation_users(self, obj): + request = self.context.get("request", None) + if request and request.query_params.get("allocation_users") in ["true", "True"]: + return AllocationUserSerializer(obj.allocationuser_set, many=True, read_only=True).data + return None + + def get_allocation_attributes(self, obj): + request = self.context.get("request", None) + if request and request.query_params.get("allocation_attributes") in ["true", "True"]: + return AllocationAttributeSerializer(obj.allocationattribute_set, many=True, read_only=True).data + return None + + +class AllocationUserSerializer(serializers.ModelSerializer): + user = serializers.SlugRelatedField(slug_field="username", read_only=True) + status = serializers.SlugRelatedField(slug_field="name", read_only=True) + + class Meta: + model = AllocationUser + fields = ("user", "status") + + +class AllocationAttributeSerializer(serializers.ModelSerializer): + allocation_attribute_type = serializers.SlugRelatedField(slug_field="name", read_only=True) + + class Meta: + model = AllocationAttribute + fields = ("allocation_attribute_type", "value") + class AllocationRequestSerializer(serializers.ModelSerializer): project = serializers.SlugRelatedField(slug_field="title", read_only=True) @@ -145,15 +178,24 @@ class Meta: fields = ("user", "role", "status") +class ProjectAttributeSerializer(serializers.ModelSerializer): + proj_attr_type = serializers.SlugRelatedField(slug_field="name", read_only=True) + + class Meta: + model = ProjectAttribute + fields = ("proj_attr_type", "value") + + class ProjectSerializer(serializers.ModelSerializer): pi = serializers.SlugRelatedField(slug_field="username", read_only=True) status = serializers.SlugRelatedField(slug_field="name", read_only=True) project_users = serializers.SerializerMethodField() allocations = serializers.SerializerMethodField() + project_attributes = serializers.SerializerMethodField() class Meta: model = Project - fields = ("id", "title", "pi", "status", "project_users", "allocations") + fields = ("id", "title", "pi", "status", "project_users", "allocations", "project_attributes") def get_project_users(self, obj): request = self.context.get("request", None) @@ -166,3 +208,9 @@ def get_allocations(self, obj): if request and request.query_params.get("allocations") in ["true", "True"]: return ProjAllocationSerializer(obj.allocation_set, many=True, read_only=True).data return None + + def get_project_attributes(self, obj): + request = self.context.get("request", None) + if request and request.query_params.get("project_attributes") in ["true", "True"]: + return ProjectAttributeSerializer(obj.projectattribute_set, many=True, read_only=True).data + return None diff --git a/coldfront/plugins/api/tests.py b/coldfront/plugins/api/tests.py index c82de56b17..bcde02d1eb 100644 --- a/coldfront/plugins/api/tests.py +++ b/coldfront/plugins/api/tests.py @@ -11,9 +11,15 @@ from coldfront.core.allocation.models import Allocation from coldfront.core.project.models import Project from coldfront.core.test_helpers.factories import ( + AllocationAttributeFactory, AllocationFactory, + AllocationUserFactory, + PAttributeTypeFactory, + ProjectAttributeFactory, + ProjectAttributeTypeFactory, ProjectFactory, ProjectStatusChoiceFactory, + ProjectUserFactory, ResourceFactory, UserFactory, ) @@ -27,16 +33,18 @@ class ColdfrontAPI(APITestCase): def setUpTestData(self): """Test Data setup for ColdFront REST API tests.""" self.admin_user = UserFactory(is_staff=True, is_superuser=True) + pat = ProjectAttributeTypeFactory(attribute_type=PAttributeTypeFactory(name="Text")) - project = ProjectFactory(status=ProjectStatusChoiceFactory(name="Active")) - allocation = AllocationFactory(project=project) - allocation.resources.add(ResourceFactory(name="test")) - self.pi_user = project.pi - - for i in range(0, 10): + for i in range(10): project = ProjectFactory(status=ProjectStatusChoiceFactory(name="Active")) + ProjectUserFactory(project=project, user=self.admin_user) + ProjectAttributeFactory(project=project, proj_attr_type=pat) + allocation = AllocationFactory(project=project) allocation.resources.add(ResourceFactory(name="test")) + AllocationUserFactory(allocation=allocation, user=self.admin_user) + AllocationAttributeFactory(allocation=allocation) + self.pi_user = project.pi def test_requires_login(self): """Test that the API requires authentication""" @@ -70,6 +78,26 @@ def test_allocation_api_permissions(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) + def test_allocation_query_params(self): + """Test that specifying the query parameters returns the related + information""" + # login as admin + self.client.force_login(self.admin_user) + response = self.client.get("/api/allocations/?allocation_users=true", format="json") + for alloc in response.json(): + self.assertEqual(len(alloc["allocation_users"]), 1) + self.assertIsNone(alloc["allocation_attributes"]) + + response = self.client.get("/api/allocations/?allocation_attributes=true", format="json") + for alloc in response.json(): + self.assertIsNone(alloc["allocation_users"]) + self.assertEqual(len(alloc["allocation_attributes"]), 1) + + response = self.client.get("/api/allocations/?allocation_users=true&allocation_attributes=true", format="json") + for alloc in response.json(): + self.assertEqual(len(alloc["allocation_users"]), 1) + self.assertEqual(len(alloc["allocation_attributes"]), 1) + def test_project_api_permissions(self): """Confirm permissions for project API: admin user should be able to access everything @@ -86,6 +114,23 @@ def test_project_api_permissions(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) + def test_project_query_params(self): + """Test that specifying the query parameters returns the related + information""" + # login as admin + self.client.force_login(self.admin_user) + response = self.client.get("/api/projects/?project_users=true", format="json") + for proj in response.json(): + self.assertEqual(len(proj["project_users"]), 1) + + response = self.client.get("/api/projects/?project_attributes=true", format="json") + for proj in response.json(): + self.assertEqual(len(proj["project_attributes"]), 1) + + response = self.client.get("/api/projects/?allocations=true", format="json") + for proj in response.json(): + self.assertEqual(len(proj["allocations"]), 1) + def test_user_api_permissions(self): """Test that accessing the user API view as an admin returns all allocations, and that accessing it as a user is forbidden""" diff --git a/coldfront/plugins/api/views.py b/coldfront/plugins/api/views.py index 83f83d3170..f18f3b73aa 100644 --- a/coldfront/plugins/api/views.py +++ b/coldfront/plugins/api/views.py @@ -51,6 +51,14 @@ class ResourceViewSet(viewsets.ReadOnlyModelViewSet): class AllocationViewSet(viewsets.ReadOnlyModelViewSet): + """ + Query parameters: + - allocation_users (default false) + Show related user data. + - allocation_attributes (default false) + Show related attribute data. + """ + serializer_class = serializers.AllocationSerializer # permission_classes = (permissions.IsAuthenticatedOrReadOnly,) @@ -71,6 +79,12 @@ def get_queryset(self): allocations = allocations.order_by("project") + if self.request.query_params.get("allocation_users") in ["True", "true"]: + allocations = allocations.prefetch_related("allocationuser_set") + + if self.request.query_params.get("allocation_attributes") in ["True", "true"]: + allocations = allocations.prefetch_related("allocationattribute_set") + return allocations @@ -271,6 +285,8 @@ class ProjectViewSet(viewsets.ReadOnlyModelViewSet): Show related allocation data. - project_users (default false) Show related user data. + - project_attributes (default false) + Show related attribute data. """ serializer_class = serializers.ProjectSerializer @@ -301,6 +317,9 @@ def get_queryset(self): if self.request.query_params.get("allocations") in ["True", "true"]: projects = projects.prefetch_related("allocation_set") + if self.request.query_params.get("project_attributes") in ["True", "true"]: + projects = projects.prefetch_related("projectattribute_set") + return projects.order_by("pi")