forked from github/plane
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
This commit is contained in:
parent
0d264838a9
commit
33a904bc3e
@ -68,4 +68,4 @@ from .importer import ImporterSerializer
|
|||||||
|
|
||||||
from .page import PageSerializer, PageBlockSerializer, PageFavoriteSerializer
|
from .page import PageSerializer, PageBlockSerializer, PageFavoriteSerializer
|
||||||
|
|
||||||
from .estimate import EstimateSerializer, EstimatePointSerializer
|
from .estimate import EstimateSerializer, EstimatePointSerializer, EstimateReadSerializer
|
||||||
|
@ -23,3 +23,16 @@ class EstimatePointSerializer(BaseSerializer):
|
|||||||
"workspace",
|
"workspace",
|
||||||
"project",
|
"project",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EstimateReadSerializer(BaseSerializer):
|
||||||
|
points = EstimatePointSerializer(read_only=True, many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Estimate
|
||||||
|
fields = "__all__"
|
||||||
|
read_only_fields = [
|
||||||
|
"points",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
@ -82,8 +82,6 @@ from plane.api.views import (
|
|||||||
StateDeleteIssueCheckEndpoint,
|
StateDeleteIssueCheckEndpoint,
|
||||||
## End States
|
## End States
|
||||||
# Estimates
|
# Estimates
|
||||||
EstimateViewSet,
|
|
||||||
EstimatePointViewSet,
|
|
||||||
ProjectEstimatePointEndpoint,
|
ProjectEstimatePointEndpoint,
|
||||||
BulkEstimatePointEndpoint,
|
BulkEstimatePointEndpoint,
|
||||||
## End Estimates
|
## End Estimates
|
||||||
@ -516,67 +514,34 @@ urlpatterns = [
|
|||||||
name="state-delete-check",
|
name="state-delete-check",
|
||||||
),
|
),
|
||||||
# End States ##
|
# End States ##
|
||||||
# States
|
# Estimates
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/",
|
|
||||||
EstimateViewSet.as_view(
|
|
||||||
{
|
|
||||||
"get": "list",
|
|
||||||
"post": "create",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
name="project-estimates",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:pk>/",
|
|
||||||
EstimateViewSet.as_view(
|
|
||||||
{
|
|
||||||
"get": "retrieve",
|
|
||||||
"put": "update",
|
|
||||||
"patch": "partial_update",
|
|
||||||
"delete": "destroy",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
name="project-estimates",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/",
|
|
||||||
EstimatePointViewSet.as_view(
|
|
||||||
{
|
|
||||||
"get": "list",
|
|
||||||
"post": "create",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
name="project-estimate-points",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/<uuid:pk>/",
|
|
||||||
EstimatePointViewSet.as_view(
|
|
||||||
{
|
|
||||||
"get": "retrieve",
|
|
||||||
"put": "update",
|
|
||||||
"patch": "partial_update",
|
|
||||||
"delete": "destroy",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
name="project-estimates",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-estimates/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/project-estimates/",
|
||||||
ProjectEstimatePointEndpoint.as_view(),
|
ProjectEstimatePointEndpoint.as_view(),
|
||||||
name="project-estimate-points",
|
name="project-estimate-points",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/bulk-estimate-points/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/",
|
||||||
BulkEstimatePointEndpoint.as_view(),
|
BulkEstimatePointEndpoint.as_view(
|
||||||
|
{
|
||||||
|
"get": "list",
|
||||||
|
"post": "create",
|
||||||
|
}
|
||||||
|
),
|
||||||
name="bulk-create-estimate-points",
|
name="bulk-create-estimate-points",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/bulk-estimate-points/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/",
|
||||||
BulkEstimatePointEndpoint.as_view(),
|
BulkEstimatePointEndpoint.as_view(
|
||||||
|
{
|
||||||
|
"get": "retrieve",
|
||||||
|
"patch": "partial_update",
|
||||||
|
"delete": "destroy",
|
||||||
|
}
|
||||||
|
),
|
||||||
name="bulk-create-estimate-points",
|
name="bulk-create-estimate-points",
|
||||||
),
|
),
|
||||||
# End States ##
|
# End Estimates ##
|
||||||
# Shortcuts
|
# Shortcuts
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/shortcuts/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/shortcuts/",
|
||||||
|
@ -133,8 +133,6 @@ from .search import GlobalSearchEndpoint, IssueSearchEndpoint
|
|||||||
from .gpt import GPTIntegrationEndpoint
|
from .gpt import GPTIntegrationEndpoint
|
||||||
|
|
||||||
from .estimate import (
|
from .estimate import (
|
||||||
EstimateViewSet,
|
|
||||||
EstimatePointViewSet,
|
|
||||||
ProjectEstimatePointEndpoint,
|
ProjectEstimatePointEndpoint,
|
||||||
BulkEstimatePointEndpoint,
|
BulkEstimatePointEndpoint,
|
||||||
)
|
)
|
||||||
|
@ -10,110 +10,11 @@ from sentry_sdk import capture_exception
|
|||||||
from .base import BaseViewSet, BaseAPIView
|
from .base import BaseViewSet, BaseAPIView
|
||||||
from plane.api.permissions import ProjectEntityPermission
|
from plane.api.permissions import ProjectEntityPermission
|
||||||
from plane.db.models import Project, Estimate, EstimatePoint
|
from plane.db.models import Project, Estimate, EstimatePoint
|
||||||
from plane.api.serializers import EstimateSerializer, EstimatePointSerializer
|
from plane.api.serializers import (
|
||||||
|
EstimateSerializer,
|
||||||
|
EstimatePointSerializer,
|
||||||
class EstimateViewSet(BaseViewSet):
|
EstimateReadSerializer,
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectEstimatePointEndpoint(BaseAPIView):
|
class ProjectEstimatePointEndpoint(BaseAPIView):
|
||||||
@ -141,12 +42,28 @@ class ProjectEstimatePointEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BulkEstimatePointEndpoint(BaseAPIView):
|
class BulkEstimatePointEndpoint(BaseViewSet):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectEntityPermission,
|
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:
|
try:
|
||||||
if not request.data.get("estimate", False):
|
if not request.data.get("estimate", False):
|
||||||
return Response(
|
return Response(
|
||||||
@ -215,14 +132,58 @@ class BulkEstimatePointEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
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:
|
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", [])):
|
if not len(request.data.get("estimate_points", [])):
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Estimate points are required"},
|
{"error": "Estimate points are required"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
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_data = request.data.get("estimate_points", [])
|
||||||
|
|
||||||
estimate_points = EstimatePoint.objects.filter(
|
estimate_points = EstimatePoint.objects.filter(
|
||||||
@ -242,18 +203,30 @@ class BulkEstimatePointEndpoint(BaseAPIView):
|
|||||||
for point in estimate_points_data
|
for point in estimate_points_data
|
||||||
if point.get("id") == str(estimate_point.id)
|
if point.get("id") == str(estimate_point.id)
|
||||||
]
|
]
|
||||||
print(estimate_point_data)
|
|
||||||
if len(estimate_point_data):
|
if len(estimate_point_data):
|
||||||
estimate_point.value = estimate_point_data[0].get(
|
estimate_point.value = estimate_point_data[0].get(
|
||||||
"value", estimate_point.value
|
"value", estimate_point.value
|
||||||
)
|
)
|
||||||
updated_estimate_points.append(estimate_point)
|
updated_estimate_points.append(estimate_point)
|
||||||
|
|
||||||
EstimatePoint.objects.bulk_update(
|
try:
|
||||||
updated_estimate_points, ["value"], batch_size=10
|
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:
|
except Estimate.DoesNotExist:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Estimate does not exist"}, status=status.HTTP_400_BAD_REQUEST
|
{"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"},
|
{"error": "Something went wrong please try again later"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
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,
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user