diff --git a/apps/api/plane/api/serializers/issue.py b/apps/api/plane/api/serializers/issue.py index f906d4085f3..f3b0ada96ab 100644 --- a/apps/api/plane/api/serializers/issue.py +++ b/apps/api/plane/api/serializers/issue.py @@ -20,6 +20,7 @@ ProjectMember, State, User, + EstimatePoint, ) from .base import BaseSerializer @@ -105,13 +106,27 @@ def validate(self, data): if ( data.get("parent") and not Issue.objects.filter( - workspace_id=self.context.get("workspace_id"), pk=data.get("parent").id + workspace_id=self.context.get("workspace_id"), + project_id=self.context.get("project_id"), + pk=data.get("parent").id, ).exists() ): raise serializers.ValidationError( "Parent is not valid issue_id please pass a valid issue_id" ) + if ( + data.get("estimate_point") + and not EstimatePoint.objects.filter( + workspace_id=self.context.get("workspace_id"), + project_id=self.context.get("project_id"), + pk=data.get("estimate_point").id, + ).exists() + ): + raise serializers.ValidationError( + "Estimate point is not valid please pass a valid estimate_point_id" + ) + return data def create(self, validated_data): diff --git a/apps/api/plane/app/serializers/draft.py b/apps/api/plane/app/serializers/draft.py index f308352633b..86a5e3686e1 100644 --- a/apps/api/plane/app/serializers/draft.py +++ b/apps/api/plane/app/serializers/draft.py @@ -1,3 +1,5 @@ +from lxml import html + # Django imports from django.utils import timezone @@ -16,7 +18,10 @@ DraftIssueLabel, DraftIssueCycle, DraftIssueModule, + ProjectMember, + EstimatePoint, ) +from plane.app.permissions import ROLE class DraftIssueCreateSerializer(BaseSerializer): @@ -57,14 +62,77 @@ def to_representation(self, instance): 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 + + try: + if attrs.get("description_html", None) is not None: + parsed = html.fromstring(attrs["description_html"]) + parsed_str = html.tostring(parsed, encoding="unicode") + attrs["description_html"] = parsed_str + + except Exception: + raise serializers.ValidationError("Invalid HTML passed") + + # Validate assignees are from project + if attrs.get("assignee_ids", []): + attrs["assignee_ids"] = ProjectMember.objects.filter( + project_id=self.context["project_id"], + role__gte=ROLE.MEMBER.value, + is_active=True, + member_id__in=attrs["assignee_ids"], + ).values_list("member_id", flat=True) + + # Validate labels are from project + if attrs.get("label_ids"): + label_ids = [label.id for label in attrs["label_ids"]] + attrs["label_ids"] = list( + Label.objects.filter( + project_id=self.context.get("project_id"), id__in=label_ids + ).values_list("id", flat=True) + ) + + # # Check state is from the project only else raise validation error + if ( + attrs.get("state") + and not State.objects.filter( + project_id=self.context.get("project_id"), + pk=attrs.get("state").id, + ).exists() + ): + raise serializers.ValidationError( + "State is not valid please pass a valid state_id" + ) + + # # Check parent issue is from workspace as it can be cross workspace + if ( + attrs.get("parent") + and not Issue.objects.filter( + project_id=self.context.get("project_id"), + pk=attrs.get("parent").id, + ).exists() + ): + raise serializers.ValidationError( + "Parent is not valid issue_id please pass a valid issue_id" + ) + + if ( + attrs.get("estimate_point") + and not EstimatePoint.objects.filter( + project_id=self.context.get("project_id"), + pk=attrs.get("estimate_point").id, + ).exists() + ): + raise serializers.ValidationError( + "Estimate point is not valid please pass a valid estimate_point_id" + ) + + return attrs def create(self, validated_data): assignees = validated_data.pop("assignee_ids", None) @@ -89,14 +157,14 @@ def create(self, validated_data): DraftIssueAssignee.objects.bulk_create( [ DraftIssueAssignee( - assignee=user, + assignee_id=assignee_id, draft_issue=issue, workspace_id=workspace_id, project_id=project_id, created_by_id=created_by_id, updated_by_id=updated_by_id, ) - for user in assignees + for assignee_id in assignees ], batch_size=10, ) @@ -105,14 +173,14 @@ def create(self, validated_data): DraftIssueLabel.objects.bulk_create( [ DraftIssueLabel( - label=label, + label_id=label_id, draft_issue=issue, project_id=project_id, workspace_id=workspace_id, created_by_id=created_by_id, updated_by_id=updated_by_id, ) - for label in labels + for label_id in labels ], batch_size=10, ) @@ -163,14 +231,14 @@ def update(self, instance, validated_data): DraftIssueAssignee.objects.bulk_create( [ DraftIssueAssignee( - assignee=user, + assignee_id=assignee_id, draft_issue=instance, workspace_id=workspace_id, project_id=project_id, created_by_id=created_by_id, updated_by_id=updated_by_id, ) - for user in assignees + for assignee_id in assignees ], batch_size=10, ) diff --git a/apps/api/plane/app/serializers/issue.py b/apps/api/plane/app/serializers/issue.py index 965d78aa2b6..7f3301126dc 100644 --- a/apps/api/plane/app/serializers/issue.py +++ b/apps/api/plane/app/serializers/issue.py @@ -1,3 +1,5 @@ +from lxml import html + # Django imports from django.utils import timezone from django.core.validators import URLValidator @@ -37,6 +39,7 @@ IssueVersion, IssueDescriptionVersion, ProjectMember, + EstimatePoint, ) @@ -119,6 +122,16 @@ def validate(self, attrs): ): raise serializers.ValidationError("Start date cannot exceed target date") + try: + if attrs.get("description_html", None) is not None: + parsed = html.fromstring(attrs["description_html"]) + parsed_str = html.tostring(parsed, encoding="unicode") + attrs["description_html"] = parsed_str + + except Exception: + raise serializers.ValidationError("Invalid HTML passed") + + # Validate assignees are from project if attrs.get("assignee_ids", []): attrs["assignee_ids"] = ProjectMember.objects.filter( project_id=self.context["project_id"], @@ -127,6 +140,51 @@ def validate(self, attrs): member_id__in=attrs["assignee_ids"], ).values_list("member_id", flat=True) + # Validate labels are from project + if attrs.get("label_ids"): + label_ids = [label.id for label in attrs["label_ids"]] + attrs["label_ids"] = list( + Label.objects.filter( + project_id=self.context.get("project_id"), + id__in=label_ids, + ).values_list("id", flat=True) + ) + + # Check state is from the project only else raise validation error + if ( + attrs.get("state") + and not State.objects.filter( + project_id=self.context.get("project_id"), + pk=attrs.get("state").id, + ).exists() + ): + raise serializers.ValidationError( + "State is not valid please pass a valid state_id" + ) + + # Check parent issue is from workspace as it can be cross workspace + if ( + attrs.get("parent") + and not Issue.objects.filter( + project_id=self.context.get("project_id"), + pk=attrs.get("parent").id, + ).exists() + ): + raise serializers.ValidationError( + "Parent is not valid issue_id please pass a valid issue_id" + ) + + if ( + attrs.get("estimate_point") + and not EstimatePoint.objects.filter( + project_id=self.context.get("project_id"), + pk=attrs.get("estimate_point").id, + ).exists() + ): + raise serializers.ValidationError( + "Estimate point is not valid please pass a valid estimate_point_id" + ) + return attrs def create(self, validated_data): @@ -190,14 +248,14 @@ def create(self, validated_data): IssueLabel.objects.bulk_create( [ IssueLabel( - label=label, + label_id=label_id, issue=issue, project_id=project_id, workspace_id=workspace_id, created_by_id=created_by_id, updated_by_id=updated_by_id, ) - for label in labels + for label_id in labels ], batch_size=10, ) @@ -243,14 +301,14 @@ def update(self, instance, validated_data): IssueLabel.objects.bulk_create( [ IssueLabel( - label=label, + label_id=label_id, issue=instance, project_id=project_id, workspace_id=workspace_id, created_by_id=created_by_id, updated_by_id=updated_by_id, ) - for label in labels + for label_id in labels ], batch_size=10, ignore_conflicts=True,