From 91ba5cb043c6cde864ed01a20b187dcb1583f208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Odini?= Date: Wed, 23 Oct 2024 09:00:05 +0200 Subject: [PATCH] =?UTF-8?q?API:=20R=C3=A9cup=C3=A9rer=20les=20WasteActions?= =?UTF-8?q?=20avec=20leur=20ResourceActions=20(si=20l'utilisateur=20est=20?= =?UTF-8?q?authentifi=C3=A9,=20filtr=C3=A9=20sur=20ses=20cantines)=20(#454?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/serializers/__init__.py | 4 +-- api/serializers/resourceaction.py | 23 +++++++++++++ api/serializers/wasteaction.py | 19 ++++++++++- api/tests/test_resource_actions.py | 7 ++-- api/tests/test_waste_actions.py | 54 +++++++++++++++++++++++++----- api/views/wasteaction.py | 8 ++++- data/models/resourceaction.py | 7 ++++ data/tests/test_resource_action.py | 23 +++++++++++-- 8 files changed, 128 insertions(+), 17 deletions(-) diff --git a/api/serializers/__init__.py b/api/serializers/__init__.py index 8539de16e..a2334ba12 100644 --- a/api/serializers/__init__.py +++ b/api/serializers/__init__.py @@ -51,5 +51,5 @@ from .communityevent import CommunityEventSerializer # noqa: F401 from .partner import PartnerSerializer, PartnerShortSerializer, PartnerContactSerializer # noqa: F401 from .videotutorial import VideoTutorialSerializer # noqa: F401 -from .wasteaction import WasteActionSerializer # noqa: F401 -from .resourceaction import ResourceActionSerializer # noqa: F401 +from .wasteaction import WasteActionSerializer, WasteActionWithActionsSerializer # noqa: F401 +from .resourceaction import ResourceActionSerializer, ResourceActionFullSerializer # noqa: F401 diff --git a/api/serializers/resourceaction.py b/api/serializers/resourceaction.py index 197aabff5..752a9b9e6 100644 --- a/api/serializers/resourceaction.py +++ b/api/serializers/resourceaction.py @@ -4,11 +4,34 @@ class ResourceActionSerializer(serializers.ModelSerializer): + resource_id = serializers.IntegerField(read_only=True) canteen_id = serializers.IntegerField(required=True) class Meta: model = ResourceAction fields = ( + "resource_id", "canteen_id", "is_done", ) + + +class ResourceActionFullSerializer(ResourceActionSerializer): + resource = serializers.SerializerMethodField(read_only=True) + canteen = serializers.SerializerMethodField(read_only=True) + + class Meta(ResourceActionSerializer.Meta): + fields = ResourceActionSerializer.Meta.fields + ( + "resource", + "canteen", + ) + + def get_resource(self, obj): + from .wasteaction import WasteActionSerializer + + return WasteActionSerializer(obj.resource).data + + def get_canteen(self, obj): + from .canteen import MinimalCanteenSerializer + + return MinimalCanteenSerializer(obj.canteen).data diff --git a/api/serializers/wasteaction.py b/api/serializers/wasteaction.py index 865e81564..61a91f0fa 100644 --- a/api/serializers/wasteaction.py +++ b/api/serializers/wasteaction.py @@ -1,7 +1,8 @@ from rest_framework import serializers from rest_framework.fields import Field -from data.models import WasteAction +from api.serializers.resourceaction import ResourceActionFullSerializer +from data.models import ResourceAction, WasteAction class WagtailImageSerializedField(Field): @@ -32,3 +33,19 @@ class Meta: "waste_origins", "lead_image", ) + + +class WasteActionWithActionsSerializer(WasteActionSerializer): + actions = serializers.SerializerMethodField() + + class Meta(WasteActionSerializer.Meta): + fields = WasteActionSerializer.Meta.fields + ("actions",) + + def get_actions(self, obj): + """Only return actions for authenticated users + related to their canteens.""" + actions = ResourceAction.objects.none() + user = self.context["request"].user + if user.is_authenticated: + actions = ResourceAction.objects.filter(resource=obj).for_user_canteens(user) + serializer = ResourceActionFullSerializer(instance=actions, many=True) + return serializer.data diff --git a/api/tests/test_resource_actions.py b/api/tests/test_resource_actions.py index 6ec448ecd..c546d3636 100644 --- a/api/tests/test_resource_actions.py +++ b/api/tests/test_resource_actions.py @@ -40,10 +40,13 @@ def test_create_resource_action(self): self.client.force_login(user=self.user_with_canteen) response = self.client.post(self.url, data={"canteen_id": self.canteen.id, "is_done": True}) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["resource_id"], self.waste_action.id) + self.assertEqual(response.data["canteen_id"], self.canteen.id) + self.assertTrue(response.data["is_done"]) self.assertEqual(ResourceAction.objects.count(), 1) self.assertEqual(ResourceAction.objects.first().resource, self.waste_action) self.assertEqual(ResourceAction.objects.first().canteen, self.canteen) - self.assertEqual(ResourceAction.objects.first().is_done, True) + self.assertTrue(ResourceAction.objects.first().is_done) def test_update_resource_action(self): # create an existing ResourceAction @@ -55,4 +58,4 @@ def test_update_resource_action(self): self.assertEqual(ResourceAction.objects.count(), 1) self.assertEqual(ResourceAction.objects.first().resource, self.waste_action) self.assertEqual(ResourceAction.objects.first().canteen, self.canteen) - self.assertEqual(ResourceAction.objects.first().is_done, False) + self.assertFalse(ResourceAction.objects.first().is_done) diff --git a/api/tests/test_waste_actions.py b/api/tests/test_waste_actions.py index adff7ccfd..929ec4788 100644 --- a/api/tests/test_waste_actions.py +++ b/api/tests/test_waste_actions.py @@ -1,11 +1,16 @@ from django.urls import reverse from rest_framework.test import APITestCase -from data.factories import WasteActionFactory +from data.factories import ( + CanteenFactory, + ResourceActionFactory, + UserFactory, + WasteActionFactory, +) from data.models import WasteAction -class TestWasteActionsApi(APITestCase): +class TestWasteActionsListApi(APITestCase): @classmethod def setUpTestData(cls): cls.waste_action = WasteActionFactory.create() @@ -15,13 +20,8 @@ def test_get_waste_actions_list(self): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data["results"]), 1) - def test_get_waste_action_detail(self): - response = self.client.get(reverse("waste_action_detail", kwargs={"pk": self.waste_action.id})) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["id"], self.waste_action.id) - -class TestWasteActionsFiltersApi(APITestCase): +class TestWasteActionsListFiltersApi(APITestCase): def test_effort_filter(self): WasteActionFactory.create(effort=WasteAction.Effort.LARGE) WasteActionFactory.create(effort=WasteAction.Effort.MEDIUM) @@ -55,3 +55,41 @@ def test_text_search(self): response = self.client.get(f"{reverse('waste_actions_list')}?search=evaluation") results = response.data["results"] self.assertEqual(len(results), 2) + + +class TestWasteActionsDetailApi(APITestCase): + @classmethod + def setUpTestData(cls): + cls.waste_action = WasteActionFactory.create() + cls.user = UserFactory() + cls.user_with_canteen = UserFactory() + CanteenFactory() + cls.canteen = CanteenFactory(managers=[cls.user_with_canteen]) + cls.resource_action = ResourceActionFactory.create( + resource=cls.waste_action, canteen=cls.canteen, is_done=True + ) + + def test_get_waste_action_detail(self): + # anonymous + response = self.client.get(reverse("waste_action_detail", kwargs={"pk": self.waste_action.id})) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["id"], self.waste_action.id) + self.assertTrue("actions" not in response.data) + # logged in user (without canteen) + self.client.force_login(user=self.user) + response = self.client.get(reverse("waste_action_detail", kwargs={"pk": self.waste_action.id})) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["id"], self.waste_action.id) + self.assertTrue("actions" in response.data) + self.assertEqual(len(response.data["actions"]), 0) + # logged in user with canteen & resource action + self.client.force_login(user=self.user_with_canteen) + response = self.client.get(reverse("waste_action_detail", kwargs={"pk": self.waste_action.id})) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["id"], self.waste_action.id) + self.assertTrue("actions" in response.data) + self.assertEqual(len(response.data["actions"]), 1) + self.assertEqual(response.data["actions"][0]["canteen_id"], self.canteen.id) + self.assertEqual(response.data["actions"][0]["canteen"]["id"], self.canteen.id) + self.assertEqual(response.data["actions"][0]["canteen"]["name"], self.canteen.name) + self.assertTrue(response.data["actions"][0]["is_done"]) diff --git a/api/views/wasteaction.py b/api/views/wasteaction.py index 5495de23c..d06f6c245 100644 --- a/api/views/wasteaction.py +++ b/api/views/wasteaction.py @@ -2,7 +2,7 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView from rest_framework.pagination import LimitOffsetPagination -from api.serializers import WasteActionSerializer +from api.serializers import WasteActionSerializer, WasteActionWithActionsSerializer from data.models import WasteAction from .utils import UnaccentSearchFilter @@ -39,3 +39,9 @@ class WasteActionView(RetrieveAPIView): model = WasteAction queryset = WasteAction.objects.all() serializer_class = WasteActionSerializer + + def get_serializer(self, *args, **kwargs): + kwargs.setdefault("context", self.get_serializer_context()) + if self.request.user.is_authenticated: + return WasteActionWithActionsSerializer(*args, **kwargs) + return super().get_serializer(*args, **kwargs) diff --git a/data/models/resourceaction.py b/data/models/resourceaction.py index e226696bd..ce117048e 100644 --- a/data/models/resourceaction.py +++ b/data/models/resourceaction.py @@ -5,12 +5,19 @@ from .wasteaction import WasteAction +class ResourceActionQuerySet(models.QuerySet): + def for_user_canteens(self, user): + return self.filter(canteen__in=user.canteens.all()) + + class ResourceAction(models.Model): class Meta: unique_together = ("resource", "canteen") verbose_name = "ressource : action (cantine)" verbose_name_plural = "ressources : actions (cantines)" + objects = models.Manager.from_queryset(ResourceActionQuerySet)() + creation_date = models.DateTimeField(auto_now_add=True) modification_date = models.DateTimeField(auto_now=True) history = HistoricalRecords() diff --git a/data/tests/test_resource_action.py b/data/tests/test_resource_action.py index be2a5c911..bc223bb1c 100644 --- a/data/tests/test_resource_action.py +++ b/data/tests/test_resource_action.py @@ -7,15 +7,32 @@ UserFactory, WasteActionFactory, ) +from data.models import ResourceAction -class ResourceActionModelSaveTest(TestCase): +class ResourceActionQuerySetTest(TestCase): @classmethod def setUpTestData(cls): - cls.waste_action = WasteActionFactory() cls.user = UserFactory() + cls.user_with_canteen = UserFactory() cls.canteen = CanteenFactory() - cls.canteen.managers.add(cls.user) + cls.canteen_with_manager = CanteenFactory(managers=[cls.user_with_canteen]) + cls.waste_action = WasteActionFactory() + ResourceActionFactory(resource=cls.waste_action, canteen=cls.canteen, is_done=True) + ResourceActionFactory(resource=cls.waste_action, canteen=cls.canteen_with_manager, is_done=True) + + def test_for_user_canteens(self): + self.assertEqual(ResourceAction.objects.count(), 2) + self.assertEqual(ResourceAction.objects.for_user_canteens(self.user).count(), 0) + self.assertEqual(ResourceAction.objects.for_user_canteens(self.user_with_canteen).count(), 1) + + +class ResourceActionModelSaveTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = UserFactory() + cls.canteen = CanteenFactory(managers=[cls.user]) + cls.waste_action = WasteActionFactory() def test_resource_action_validation(self): # NOT OK: missing required field