From 40c0bbcfb40ad57a391ae8c5dde90522d99f04dc Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:08:34 +0530 Subject: [PATCH] fix: assignee validation when updating issues (#6720) * fix: assignee validation * chore: remove prints * fix: remove all assignees --- apiserver/plane/app/serializers/issue.py | 55 ++++++++++++------------ apiserver/plane/app/views/intake/base.py | 6 ++- apiserver/plane/app/views/issue/base.py | 16 +++---- apiserver/plane/space/views/intake.py | 9 +++- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index 1b754ea6a..b9306a728 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -111,25 +111,23 @@ class IssueCreateSerializer(BaseSerializer): data["label_ids"] = label_ids if label_ids else [] return data - def validate(self, data): + def validate(self, attrs): if ( - data.get("start_date", None) is not None - and data.get("target_date", None) is not None - and data.get("start_date", None) > data.get("target_date", None) + attrs.get("start_date", None) is not None + and attrs.get("target_date", None) is not None + and attrs.get("start_date", None) > attrs.get("target_date", None) ): raise serializers.ValidationError("Start date cannot exceed target date") - return data - def get_valid_assignees(self, assignees, project_id): - if not assignees: - return [] + if attrs.get("assignee_ids", []): + attrs["assignee_ids"] = ProjectMember.objects.filter( + project_id=self.context["project_id"], + role__gte=15, + is_active=True, + member_id__in=attrs["assignee_ids"], + ).values_list("member_id", flat=True) - return ProjectMember.objects.filter( - project_id=project_id, - role__gte=15, - is_active=True, - member_id__in=assignees - ).values_list('member_id', flat=True) + return attrs def create(self, validated_data): assignees = validated_data.pop("assignee_ids", None) @@ -146,20 +144,19 @@ class IssueCreateSerializer(BaseSerializer): created_by_id = issue.created_by_id updated_by_id = issue.updated_by_id - valid_assignee_ids = self.get_valid_assignees(assignees, project_id) - if valid_assignee_ids is not None and len(valid_assignee_ids): + if assignees is not None and len(assignees): try: IssueAssignee.objects.bulk_create( [ IssueAssignee( - assignee_id=user_id, + assignee_id=assignee_id, issue=issue, project_id=project_id, workspace_id=workspace_id, created_by_id=created_by_id, updated_by_id=updated_by_id, ) - for user_id in valid_assignee_ids + for assignee_id in assignees ], batch_size=10, ) @@ -167,12 +164,15 @@ class IssueCreateSerializer(BaseSerializer): pass else: # Then assign it to default assignee, if it is a valid assignee - if default_assignee_id is not None and ProjectMember.objects.filter( - member_id=default_assignee_id, - project_id=project_id, - role__gte=15, - is_active=True - ).exists(): + if ( + default_assignee_id is not None + and ProjectMember.objects.filter( + member_id=default_assignee_id, + project_id=project_id, + role__gte=15, + is_active=True, + ).exists() + ): try: IssueAssignee.objects.create( assignee_id=default_assignee_id, @@ -216,21 +216,20 @@ class IssueCreateSerializer(BaseSerializer): created_by_id = instance.created_by_id updated_by_id = instance.updated_by_id - valid_assignee_ids = self.get_valid_assignees(assignees, project_id) - if valid_assignee_ids is not None: + if assignees is not None: IssueAssignee.objects.filter(issue=instance).delete() try: IssueAssignee.objects.bulk_create( [ IssueAssignee( - assignee_id=user_id, + assignee_id=assignee_id, issue=instance, project_id=project_id, workspace_id=workspace_id, created_by_id=created_by_id, updated_by_id=updated_by_id, ) - for user_id in valid_assignee_ids + for assignee_id in assignees ], batch_size=10, ignore_conflicts=True, diff --git a/apiserver/plane/app/views/intake/base.py b/apiserver/plane/app/views/intake/base.py index 631fe80da..fb10bc002 100644 --- a/apiserver/plane/app/views/intake/base.py +++ b/apiserver/plane/app/views/intake/base.py @@ -178,7 +178,9 @@ class IntakeIssueViewSet(BaseViewSet): workspace__slug=slug, project_id=project_id ).first() if not intake: - return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND) + return Response( + {"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND + ) project = Project.objects.get(pk=project_id) filters = issue_filters(request.GET, "GET", "issue__") @@ -385,7 +387,7 @@ class IntakeIssueViewSet(BaseViewSet): } issue_serializer = IssueCreateSerializer( - issue, data=issue_data, partial=True + issue, data=issue_data, partial=True, context={"project_id": project_id} ) if issue_serializer.is_valid(): diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 48ea2f6bc..79ffe35d8 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -635,7 +635,9 @@ class IssueViewSet(BaseViewSet): ) requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) - serializer = IssueCreateSerializer(issue, data=request.data, partial=True) + serializer = IssueCreateSerializer( + issue, data=request.data, partial=True, context={"project_id": project_id} + ) if serializer.is_valid(): serializer.save() issue_activity.delay( @@ -1099,7 +1101,6 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView): class IssueMetaEndpoint(BaseAPIView): - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="PROJECT") def get(self, request, slug, project_id, issue_id): issue = Issue.issue_objects.only("sequence_id", "project__identifier").get( @@ -1115,14 +1116,12 @@ class IssueMetaEndpoint(BaseAPIView): class IssueDetailIdentifierEndpoint(BaseAPIView): - def strict_str_to_int(self, s): - if not s.isdigit() and not (s.startswith('-') and s[1:].isdigit()): + if not s.isdigit() and not (s.startswith("-") and s[1:].isdigit()): raise ValueError("Invalid integer string") return int(s) def get(self, request, slug, project_identifier, issue_identifier): - # Check if the issue identifier is a valid integer try: issue_identifier = self.strict_str_to_int(issue_identifier) @@ -1134,8 +1133,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView): # Fetch the project project = Project.objects.get( - identifier__iexact=project_identifier, - workspace__slug=slug, + identifier__iexact=project_identifier, workspace__slug=slug ) # Check if the user is a member of the project @@ -1237,8 +1235,8 @@ class IssueDetailIdentifierEndpoint(BaseAPIView): .annotate( is_subscribed=Exists( IssueSubscriber.objects.filter( - workspace__slug=slug, - project_id=project.id, + workspace__slug=slug, + project_id=project.id, issue__sequence_id=issue_identifier, subscriber=request.user, ) diff --git a/apiserver/plane/space/views/intake.py b/apiserver/plane/space/views/intake.py index bfce3a8bb..644a2de3a 100644 --- a/apiserver/plane/space/views/intake.py +++ b/apiserver/plane/space/views/intake.py @@ -12,7 +12,7 @@ from rest_framework.response import Response # Module imports from .base import BaseViewSet -from plane.db.models import IntakeIssue, Issue, State, IssueLink, FileAsset, DeployBoard +from plane.db.models import IntakeIssue, Issue, IssueLink, FileAsset, DeployBoard from plane.app.serializers import ( IssueSerializer, IntakeIssueSerializer, @@ -202,7 +202,12 @@ class IntakeIssuePublicViewSet(BaseViewSet): "description": issue_data.get("description", issue.description), } - issue_serializer = IssueCreateSerializer(issue, data=issue_data, partial=True) + issue_serializer = IssueCreateSerializer( + issue, + data=issue_data, + partial=True, + context={"project_id": project_deploy_board.project_id}, + ) if issue_serializer.is_valid(): current_instance = issue