From 7c05252e0003018e0680d8f43b4cabf990d3dd1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Cant=C3=B9?= Date: Wed, 20 Nov 2024 11:26:59 +0100 Subject: [PATCH] allow bulk delete of samples --- src/frontend/src/samples/components/Table.jsx | 40 +++++++++++++-- src/genlab_bestilling/api/views.py | 51 ++++++++++++++----- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/samples/components/Table.jsx b/src/frontend/src/samples/components/Table.jsx index 82cf593..e4b33ae 100644 --- a/src/frontend/src/samples/components/Table.jsx +++ b/src/frontend/src/samples/components/Table.jsx @@ -23,6 +23,10 @@ import { AxiosError } from "axios"; import NumberCellInput from "./Cell/NumberCellInput"; // import MultiSelectCell from "./Cell/MultiSelectCell"; +function askConfirm(fn) { + return () => confirm('Are you really sure?') && fn() +} + async function getSamples({ pageParam }) { const url = pageParam || `/api/samples/?order=${config.order}`; const response = await client.get(url); @@ -150,6 +154,8 @@ const COLUMNS = [ ].filter((_) => _); function handleError(e) { + console.error(e) + if (e instanceof AxiosError) { e.response.data.errors.forEach((err) => { toast.error(err.detail); @@ -240,6 +246,27 @@ export default function Table() { onError: handleError, }); + const mutateDeleteAllRows = useMutation({ + mutationFn: () => { + return client.post(`/api/analysis-order/${config.order}/delete-samples/`); + }, + onSuccess: () => { + toast.success("Samples deleted!"); + queryClient.invalidateQueries({ queryKey: ["samples"] }); + }, + onError: (e) => { + console.error(e) + toast.error("There was an error!"); + }, + }); + + const pendingState = isLoading || + isFetching || + mutateDeleteAllRows.isPending || + mutateConfirm.isPending || + deleteRow.isPending || + updateCell.isPending + const table = useReactTable({ data: flatData, columns: COLUMNS, @@ -348,8 +375,7 @@ export default function Table() {
{(isLoading || isFetching || - updateCell.isPending || - deleteRow.isPending) && ( + pendingState) && ( )}
@@ -376,11 +402,19 @@ export default function Table() { + ); diff --git a/src/genlab_bestilling/api/views.py b/src/genlab_bestilling/api/views.py index 82c17ac..5c073a0 100644 --- a/src/genlab_bestilling/api/views.py +++ b/src/genlab_bestilling/api/views.py @@ -42,23 +42,28 @@ def has_object_permission(self, request, view, obj): class SampleViewset(ModelViewSet): - queryset = ( - Sample.objects.all() - .select_related( - "type", - "species", - "order", - "order__genrequest", - "order__genrequest__area", - "location", - ) - .order_by("id") - ) + queryset = Sample.objects.all() serializer_class = SampleSerializer filterset_class = SampleFilter pagination_class = IDCursorPagination permission_classes = [AllowSampleDraft, IsAuthenticated] + def get_queryset(self): + return ( + super() + .get_queryset() + .filter_allowed(self.request.user) + .select_related( + "type", + "species", + "order", + "order__genrequest", + "order__genrequest__area", + "location", + ) + .order_by("id") + ) + def get_serializer_class(self): if self.action in ["update", "partial_update"]: return SampleUpdateSerializer @@ -85,6 +90,8 @@ def bulk_create(self, request): } order = serializer.validated_data["order"] + # TODO: raise validation issue if order is not "owned" + # by a project the user is part of samples = [] with transaction.atomic(): for i in range(qty): @@ -119,6 +126,12 @@ class AnalysisOrderViewset( queryset = AnalysisOrder.objects.all() serializer_class = AnalysisSerializer + def get_queryset(self): + qs = super().get_queryset().filter_allowed(self.request.user) + if self.request.method not in SAFE_METHODS: + qs = qs.filter_in_draft() + return qs + @action( methods=["POST"], url_path="confirm", @@ -130,6 +143,20 @@ def confirm_order(self, request, pk): obj.confirm_order(persist=False) return Response(self.get_serializer(obj).data) + @extend_schema(responses={200: OperationStatusSerializer}) + @action( + methods=["POST"], + url_path="delete-samples", + detail=True, + permission_classes=[AllowOrderDraft], + ) + def bulk_delete(self, request, pk): + obj = self.get_object() + with transaction.atomic(): + obj.samples.all().delete() + + return Response(data=OperationStatusSerializer({"success": True}).data) + class SampleTypeViewset(mixins.ListModelMixin, GenericViewSet): queryset = SampleType.objects.all().order_by("name")