From 33a904bc3e4999b751d267014a3802ac4aa08109 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Sat, 22 Apr 2023 01:04:20 +0530 Subject: [PATCH] chore: added estimate bulk endpoint for retrieving and updating (#919) * chore: added estimate bulk endpoint for retrieving and updating * chore: estimate endpoints * fix: retrieve project estimate * dev: handle integrity error check --- apiserver/plane/api/serializers/__init__.py | 2 +- apiserver/plane/api/serializers/estimate.py | 13 ++ apiserver/plane/api/urls.py | 69 ++----- apiserver/plane/api/views/__init__.py | 2 - apiserver/plane/api/views/estimate.py | 211 +++++++++----------- 5 files changed, 130 insertions(+), 167 deletions(-) diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 2adff8299..1cc866e8c 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -68,4 +68,4 @@ from .importer import ImporterSerializer from .page import PageSerializer, PageBlockSerializer, PageFavoriteSerializer -from .estimate import EstimateSerializer, EstimatePointSerializer +from .estimate import EstimateSerializer, EstimatePointSerializer, EstimateReadSerializer diff --git a/apiserver/plane/api/serializers/estimate.py b/apiserver/plane/api/serializers/estimate.py index 0aa4d331e..360275562 100644 --- a/apiserver/plane/api/serializers/estimate.py +++ b/apiserver/plane/api/serializers/estimate.py @@ -23,3 +23,16 @@ class EstimatePointSerializer(BaseSerializer): "workspace", "project", ] + + +class EstimateReadSerializer(BaseSerializer): + points = EstimatePointSerializer(read_only=True, many=True) + + class Meta: + model = Estimate + fields = "__all__" + read_only_fields = [ + "points", + "name", + "description", + ] diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 2c202a1c0..364b959e3 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -82,8 +82,6 @@ from plane.api.views import ( StateDeleteIssueCheckEndpoint, ## End States # Estimates - EstimateViewSet, - EstimatePointViewSet, ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, ## End Estimates @@ -516,67 +514,34 @@ urlpatterns = [ name="state-delete-check", ), # End States ## - # States - path( - "workspaces//projects//estimates/", - EstimateViewSet.as_view( - { - "get": "list", - "post": "create", - } - ), - name="project-estimates", - ), - path( - "workspaces//projects//estimates//", - EstimateViewSet.as_view( - { - "get": "retrieve", - "put": "update", - "patch": "partial_update", - "delete": "destroy", - } - ), - name="project-estimates", - ), - path( - "workspaces//projects//estimates//estimate-points/", - EstimatePointViewSet.as_view( - { - "get": "list", - "post": "create", - } - ), - name="project-estimate-points", - ), - path( - "workspaces//projects//estimates//estimate-points//", - EstimatePointViewSet.as_view( - { - "get": "retrieve", - "put": "update", - "patch": "partial_update", - "delete": "destroy", - } - ), - name="project-estimates", - ), + # Estimates path( "workspaces//projects//project-estimates/", ProjectEstimatePointEndpoint.as_view(), name="project-estimate-points", ), path( - "workspaces//projects//estimates/bulk-estimate-points/", - BulkEstimatePointEndpoint.as_view(), + "workspaces//projects//estimates/", + BulkEstimatePointEndpoint.as_view( + { + "get": "list", + "post": "create", + } + ), name="bulk-create-estimate-points", ), path( - "workspaces//projects//estimates//bulk-estimate-points/", - BulkEstimatePointEndpoint.as_view(), + "workspaces//projects//estimates//", + BulkEstimatePointEndpoint.as_view( + { + "get": "retrieve", + "patch": "partial_update", + "delete": "destroy", + } + ), name="bulk-create-estimate-points", ), - # End States ## + # End Estimates ## # Shortcuts path( "workspaces//projects//shortcuts/", diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 18809cd9d..83725e104 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -133,8 +133,6 @@ from .search import GlobalSearchEndpoint, IssueSearchEndpoint from .gpt import GPTIntegrationEndpoint from .estimate import ( - EstimateViewSet, - EstimatePointViewSet, ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, ) diff --git a/apiserver/plane/api/views/estimate.py b/apiserver/plane/api/views/estimate.py index 99374282d..e878ccafc 100644 --- a/apiserver/plane/api/views/estimate.py +++ b/apiserver/plane/api/views/estimate.py @@ -10,110 +10,11 @@ from sentry_sdk import capture_exception from .base import BaseViewSet, BaseAPIView from plane.api.permissions import ProjectEntityPermission from plane.db.models import Project, Estimate, EstimatePoint -from plane.api.serializers import EstimateSerializer, EstimatePointSerializer - - -class EstimateViewSet(BaseViewSet): - permission_classes = [ - ProjectEntityPermission, - ] - model = Estimate - serializer_class = EstimateSerializer - - def get_queryset(self): - return ( - super() - .get_queryset() - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(project_id=self.kwargs.get("project_id")) - .filter(project__project_projectmember__member=self.request.user) - .select_related("project") - .select_related("workspace") - .distinct() - ) - - def perform_create(self, serializer): - serializer.save(project_id=self.kwargs.get("project_id")) - - -class EstimatePointViewSet(BaseViewSet): - permission_classes = [ - ProjectEntityPermission, - ] - model = EstimatePoint - serializer_class = EstimatePointSerializer - - def get_queryset(self): - return ( - super() - .get_queryset() - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(project_id=self.kwargs.get("project_id")) - .filter(project__project_projectmember__member=self.request.user) - .filter(estimate_id=self.kwargs.get("estimate_id")) - .select_related("project") - .select_related("workspace") - .distinct() - ) - - def perform_create(self, serializer): - serializer.save( - project_id=self.kwargs.get("project_id"), - estimate_id=self.kwargs.get("estimate_id"), - ) - - def create(self, request, slug, project_id, estimate_id): - try: - serializer = EstimatePointSerializer(data=request.data) - if serializer.is_valid(): - serializer.save(estimate_id=estimate_id, project_id=project_id) - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - except IntegrityError as e: - if "already exists" in str(e): - return Response( - {"error": "The estimate point is already taken"}, - status=status.HTTP_410_GONE, - ) - else: - capture_exception(e) - return Response( - {"error": "Something went wrong please try again later"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - def partial_update(self, request, slug, project_id, estimate_id, pk): - try: - estimate_point = EstimatePoint.objects.get( - pk=pk, - estimate_id=estimate_id, - project_id=project_id, - workspace__slug=slug, - ) - serializer = EstimatePointSerializer( - estimate_point, data=request.data, partial=True - ) - if serializer.is_valid(): - serializer.save(estimate_id=estimate_id, project_id=project_id) - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - except EstimatePoint.DoesNotExist: - return Response( - {"error": "Estimate Point does not exist"}, - status=status.HTTP_404_NOT_FOUND, - ) - except IntegrityError as e: - if "already exists" in str(e): - return Response( - {"error": "The estimate point value is already taken"}, - status=status.HTTP_410_GONE, - ) - else: - capture_exception(e) - return Response( - {"error": "Something went wrong please try again later"}, - status=status.HTTP_400_BAD_REQUEST, - ) +from plane.api.serializers import ( + EstimateSerializer, + EstimatePointSerializer, + EstimateReadSerializer, +) class ProjectEstimatePointEndpoint(BaseAPIView): @@ -141,12 +42,28 @@ class ProjectEstimatePointEndpoint(BaseAPIView): ) -class BulkEstimatePointEndpoint(BaseAPIView): +class BulkEstimatePointEndpoint(BaseViewSet): permission_classes = [ ProjectEntityPermission, ] + model = Estimate + serializer_class = EstimateSerializer - def post(self, request, slug, project_id): + def list(self, request, slug, project_id): + try: + estimates = Estimate.objects.filter( + workspace__slug=slug, project_id=project_id + ).prefetch_related("points") + serializer = EstimateReadSerializer(estimates, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + print(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def create(self, request, slug, project_id): try: if not request.data.get("estimate", False): return Response( @@ -215,14 +132,58 @@ class BulkEstimatePointEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) - def patch(self, request, slug, project_id, estimate_id): + def retrieve(self, request, slug, project_id, estimate_id): try: + estimate = Estimate.objects.get( + pk=estimate_id, workspace__slug=slug, project_id=project_id + ) + serializer = EstimateReadSerializer(estimate) + return Response( + serializer.data, + status=status.HTTP_200_OK, + ) + except Estimate.DoesNotExist: + return Response( + {"error": "Estimate does not exist"}, status=status.HTTP_400_BAD_REQUEST + ) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def partial_update(self, request, slug, project_id, estimate_id): + try: + if not request.data.get("estimate", False): + return Response( + {"error": "Estimate is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + if not len(request.data.get("estimate_points", [])): return Response( {"error": "Estimate points are required"}, status=status.HTTP_400_BAD_REQUEST, ) + estimate = Estimate.objects.get(pk=estimate_id) + + estimate_serializer = EstimateSerializer( + estimate, data=request.data.get("estimate"), partial=True + ) + if not estimate_serializer.is_valid(): + return Response( + estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) + try: + estimate = estimate_serializer.save() + except IntegrityError: + return Response( + {"errror": "Estimate with the name already exists"}, + status=status.HTTP_400_BAD_REQUEST, + ) + estimate_points_data = request.data.get("estimate_points", []) estimate_points = EstimatePoint.objects.filter( @@ -242,18 +203,30 @@ class BulkEstimatePointEndpoint(BaseAPIView): for point in estimate_points_data if point.get("id") == str(estimate_point.id) ] - print(estimate_point_data) if len(estimate_point_data): estimate_point.value = estimate_point_data[0].get( "value", estimate_point.value ) updated_estimate_points.append(estimate_point) - EstimatePoint.objects.bulk_update( - updated_estimate_points, ["value"], batch_size=10 + try: + EstimatePoint.objects.bulk_update( + updated_estimate_points, ["value"], batch_size=10 + ) + except IntegrityError as e: + return Response( + {"error": "Values need to be unique for each key"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + estimate_point_serializer = EstimatePointSerializer(estimate_points, many=True) + return Response( + { + "estimate": estimate_serializer.data, + "estimate_points": estimate_point_serializer.data, + }, + status=status.HTTP_200_OK, ) - serializer = EstimatePointSerializer(estimate_points, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) except Estimate.DoesNotExist: return Response( {"error": "Estimate does not exist"}, status=status.HTTP_400_BAD_REQUEST @@ -264,3 +237,17 @@ class BulkEstimatePointEndpoint(BaseAPIView): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + def destroy(self, request, slug, project_id, estimate_id): + try: + estimate = Estimate.objects.get( + pk=estimate_id, workspace__slug=slug, project_id=project_id + ) + estimate.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + )