Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TreatmentPrescription bulk delete endpoint + tests (PLAN-1504) #1670

Merged
merged 4 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/planscape/impacts/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ def has_permission(self, request, view):
match view.action:
case "create":
return TreatmentPlanPermission.can_add_scenario(request.user, tx_plan)
case "batch_delete":
return TreatmentPlanPermission.can_remove(request.user, tx_plan)
case _:
return TreatmentPlanPermission.can_view(request.user, tx_plan)

Expand Down
6 changes: 6 additions & 0 deletions src/planscape/impacts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ class Meta:
)


class TreatmentPrescriptionBatchDeleteSerializer(serializers.Serializer):
stand_ids = serializers.ListField(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much nicer!

child=serializers.IntegerField(), allow_empty=False
)


class SummarySerializer(serializers.Serializer):
project_area = serializers.PrimaryKeyRelatedField(
queryset=ProjectArea.objects.all(),
Expand Down
90 changes: 89 additions & 1 deletion src/planscape/impacts/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
from collaboration.models import Permissions, Role, UserObjectRole
from collaboration.services import get_content_type
from impacts.models import TreatmentPlan
from impacts.tests.factories import TreatmentPlanFactory, TreatmentPrescriptionFactory
from impacts.tests.factories import (
TreatmentPlanFactory,
TreatmentPrescriptionFactory,
)
from planning.tests.factories import ScenarioFactory
from planscape.tests.factories import UserFactory

Expand Down Expand Up @@ -205,3 +208,88 @@ def test_list_tx_rx(self):
data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(data["count"], 2)


class TxPrescriptionBatchDeleteTest(APITransactionTestCase):
def setUp(self):
self.tx_plan = TreatmentPlanFactory.create()
self.alt_tx_plan = TreatmentPlanFactory.create()
self.client.force_authenticate(user=self.tx_plan.scenario.user)
self.txrx_owned_list = TreatmentPrescriptionFactory.create_batch(
10, treatment_plan=self.tx_plan
)
# plans for a different user
self.txrx_other_list = TreatmentPrescriptionFactory.create_batch(
10, treatment_plan=self.alt_tx_plan
)

def test_batch_delete_tx_rx(self):
payload = {"stand_ids": [txrx.stand_id for txrx in self.txrx_owned_list]}
response = self.client.post(
reverse(
"api:impacts:tx-prescriptions-delete-prescriptions",
kwargs={"tx_plan_pk": self.tx_plan.pk},
),
data=payload,
format="json",
)
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response_data["result"][0], 10)

def test_batch_delete_tx_rx_bad_values(self):
payload = {"stand_ids": [None]}
response = self.client.post(
reverse(
"api:impacts:tx-prescriptions-delete-prescriptions",
kwargs={"tx_plan_pk": self.tx_plan.pk},
),
data=payload,
format="json",
)
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_batch_delete_tx_rx_empty_list(self):
payload = {"stand_ids": []}
response = self.client.post(
reverse(
"api:impacts:tx-prescriptions-delete-prescriptions",
kwargs={"tx_plan_pk": self.tx_plan.pk},
),
data=payload,
format="json",
)
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_batch_delete_nonowned_tx_rx(self):
payload = {"stand_ids": [txrx.stand_id for txrx in self.txrx_other_list]}
response = self.client.post(
reverse(
"api:impacts:tx-prescriptions-delete-prescriptions",
kwargs={"tx_plan_pk": self.alt_tx_plan.pk},
),
data=payload,
format="json",
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

# Testing a request to delete various stand_ids for some txrx records don't match the treatment plan id
# current behavior is to only delete items matching the given tx_plan and quietly ignore the rest
def test_batch_delete_mixed_tx_rx(self):
owned_ids = [txrx.stand_id for txrx in self.txrx_owned_list]
other_ids = [txrx.stand_id for txrx in self.txrx_other_list]

payload = {"stand_ids": owned_ids + other_ids}
response = self.client.post(
reverse(
"api:impacts:tx-prescriptions-delete-prescriptions",
kwargs={"tx_plan_pk": self.tx_plan.pk},
),
data=payload,
format="json",
)
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response_data["result"][0], 10)
18 changes: 18 additions & 0 deletions src/planscape/impacts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
TreatmentPlanSerializer,
TreatmentPrescriptionSerializer,
TreatmentPrescriptionListSerializer,
TreatmentPrescriptionBatchDeleteSerializer,
UpsertTreamentPrescriptionSerializer,
)
from impacts.services import (
Expand Down Expand Up @@ -191,3 +192,20 @@ def create(self, request, *args, **kwargs):

def perform_create(self, serializer):
return upsert_treatment_prescriptions(**serializer.validated_data)

@action(detail=False, methods=["post"])
def delete_prescriptions(self, request, tx_plan_pk=None):
serializer = TreatmentPrescriptionBatchDeleteSerializer(data=request.data)

if not serializer.is_valid():
return response.Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)

stand_ids = serializer.validated_data.get("stand_ids", [])

delete_result = TreatmentPrescription.objects.filter(
stand_id__in=stand_ids, treatment_plan_id=tx_plan_pk
).delete()
Comment on lines +207 to +209
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a product question to do to Jordan: in other endpoints we record an actstream about deletions. Should we log this interaction as well?

If the answer is yes, then the actstrem format is a bit different, but it should be possible to log it with a method on our service module.


return response.Response({"result": delete_result}, status=status.HTTP_200_OK)
Loading