[SILO-723] feat: add options in custom property during create flow (#4893)
* [SILO-723] feat: add options in custom property in create property API - To update or add more options, users have to use the dedicated options endpoint * add validation for issue type and project during property creation * pass type and project context in update apis
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Django imports
|
||||
from django.db import models, transaction
|
||||
|
||||
# Module imports
|
||||
from rest_framework import serializers
|
||||
from plane.db.models import ProjectIssueType
|
||||
from plane.ee.serializers import BaseSerializer
|
||||
from plane.ee.models import (
|
||||
IssueProperty,
|
||||
@@ -7,12 +11,35 @@ from plane.ee.models import (
|
||||
IssuePropertyValue,
|
||||
IssuePropertyActivity,
|
||||
RelationTypeEnum,
|
||||
PropertyTypeEnum,
|
||||
)
|
||||
|
||||
|
||||
class IssuePropertyOptionAPISerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = IssuePropertyOption
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"sort_order",
|
||||
"property",
|
||||
"logo_props",
|
||||
"deleted_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
|
||||
|
||||
class IssuePropertyAPISerializer(BaseSerializer):
|
||||
relation_type = serializers.ChoiceField(
|
||||
choices=RelationTypeEnum.choices, required=False, allow_null=True
|
||||
relation_type = serializers.ChoiceField(choices=RelationTypeEnum.choices, required=False, allow_null=True)
|
||||
options = serializers.ListField(
|
||||
child=IssuePropertyOptionAPISerializer(),
|
||||
required=False,
|
||||
write_only=True,
|
||||
help_text="List of options to create when property_type is OPTION. Each option should have 'name', optionally 'description', 'is_default', 'external_id', and 'external_source'.",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -32,23 +59,113 @@ class IssuePropertyAPISerializer(BaseSerializer):
|
||||
"updated_by",
|
||||
]
|
||||
|
||||
def to_representation(self, instance):
|
||||
"""Override to add options field to response"""
|
||||
representation = super().to_representation(instance)
|
||||
# Add options field for OPTION type properties
|
||||
if instance.property_type == PropertyTypeEnum.OPTION:
|
||||
options = IssuePropertyOption.objects.filter(
|
||||
property=instance,
|
||||
deleted_at__isnull=True,
|
||||
).order_by("sort_order")
|
||||
representation["options"] = IssuePropertyOptionAPISerializer(options, many=True).data
|
||||
else:
|
||||
representation["options"] = []
|
||||
return representation
|
||||
|
||||
class IssuePropertyOptionAPISerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = IssuePropertyOption
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"sort_order",
|
||||
"property",
|
||||
"logo_props",
|
||||
"deleted_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
]
|
||||
def validate_options(self, value):
|
||||
"""Validate options field - ensure it's only used with OPTION property type"""
|
||||
if not value:
|
||||
return value
|
||||
|
||||
# Get property_type from the initial data (since this is field-level validation)
|
||||
property_type = self.initial_data.get("property_type")
|
||||
|
||||
if property_type != PropertyTypeEnum.OPTION:
|
||||
raise serializers.ValidationError("Options can only be provided when property_type is OPTION")
|
||||
|
||||
# Validate that only one option is marked as default
|
||||
# Note: Individual option validation (name, etc.) is handled by IssuePropertyOptionAPISerializer
|
||||
default_count = sum(1 for option_data in value if option_data.get("is_default", False))
|
||||
|
||||
if default_count > 1:
|
||||
raise serializers.ValidationError("Only one option can be marked as default")
|
||||
|
||||
return value
|
||||
|
||||
def validate(self, data):
|
||||
"""Validate the serializer data"""
|
||||
|
||||
# validate if the issue type is in the project sent in the request
|
||||
issue_type = self.context.get("issue_type")
|
||||
project = self.context.get("project")
|
||||
|
||||
if issue_type and project:
|
||||
project_issue_type = ProjectIssueType.objects.filter(issue_type=issue_type, project=project).first()
|
||||
if not project_issue_type:
|
||||
raise serializers.ValidationError("Issue type is not in the project")
|
||||
|
||||
return data
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
"""Override create to handle options creation"""
|
||||
options_data = validated_data.pop("options", [])
|
||||
property_type = validated_data.get("property_type")
|
||||
|
||||
# Create the property
|
||||
issue_property = super().create(validated_data)
|
||||
|
||||
# Create options if property_type is OPTION and options are provided
|
||||
if property_type == PropertyTypeEnum.OPTION and options_data:
|
||||
# Get workspace and project from the created property
|
||||
workspace = issue_property.workspace
|
||||
project = issue_property.project
|
||||
|
||||
# Get the last sort order
|
||||
last_sort_order = IssuePropertyOption.objects.filter(
|
||||
project=project,
|
||||
property=issue_property,
|
||||
).aggregate(largest=models.Max("sort_order"))["largest"]
|
||||
|
||||
sort_order = last_sort_order + 10000 if last_sort_order is not None else 10000
|
||||
|
||||
# Create options using the nested serializer's validated data
|
||||
for option_validated_data in options_data:
|
||||
# Check for external_id conflicts
|
||||
external_id = option_validated_data.get("external_id")
|
||||
external_source = option_validated_data.get("external_source")
|
||||
if external_id and external_source:
|
||||
existing_option = IssuePropertyOption.objects.filter(
|
||||
workspace=workspace,
|
||||
project=project,
|
||||
property=issue_property,
|
||||
external_source=external_source,
|
||||
external_id=external_id,
|
||||
deleted_at__isnull=True,
|
||||
).first()
|
||||
if existing_option:
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"options": f"Option with external_id '{external_id}' and external_source '{external_source}' already exists"
|
||||
}
|
||||
)
|
||||
|
||||
# Create the option using validated data from nested serializer
|
||||
IssuePropertyOption.objects.create(
|
||||
workspace=workspace,
|
||||
project=project,
|
||||
property=issue_property,
|
||||
name=option_validated_data.get("name"),
|
||||
description=option_validated_data.get("description", ""),
|
||||
is_default=option_validated_data.get("is_default", False),
|
||||
external_id=external_id,
|
||||
external_source=external_source,
|
||||
sort_order=sort_order,
|
||||
)
|
||||
sort_order += 10000
|
||||
|
||||
return issue_property
|
||||
|
||||
|
||||
class IssuePropertyValueAPISerializer(BaseSerializer):
|
||||
@@ -159,9 +276,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
|
||||
# Check if required
|
||||
if property_obj.is_required and not value:
|
||||
raise serializers.ValidationError(
|
||||
f"{property_obj.display_name} is required"
|
||||
)
|
||||
raise serializers.ValidationError(f"{property_obj.display_name} is required")
|
||||
|
||||
# If value is empty and not required, no further validation needed
|
||||
if not value and value != 0 and value is not False:
|
||||
@@ -182,9 +297,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
try:
|
||||
url_validator(value)
|
||||
except DjangoValidationError:
|
||||
raise serializers.ValidationError(
|
||||
"Must be a valid URL (e.g., https://example.com)"
|
||||
)
|
||||
raise serializers.ValidationError("Must be a valid URL (e.g., https://example.com)")
|
||||
|
||||
# EMAIL - must be valid email format
|
||||
elif property_type == PropertyTypeEnum.EMAIL:
|
||||
@@ -193,9 +306,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
try:
|
||||
validate_email(value)
|
||||
except DjangoValidationError:
|
||||
raise serializers.ValidationError(
|
||||
"Must be a valid email address (e.g., [email protected])"
|
||||
)
|
||||
raise serializers.ValidationError("Must be a valid email address (e.g., [email protected])")
|
||||
|
||||
# DATETIME - accepts YYYY-MM-DD or YYYY-MM-DD HH:MM:SS
|
||||
elif property_type == PropertyTypeEnum.DATETIME:
|
||||
@@ -212,9 +323,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
continue
|
||||
|
||||
if not parsed:
|
||||
raise serializers.ValidationError(
|
||||
"Must be a valid date in format YYYY-MM-DD or YYYY-MM-DD HH:MM:SS"
|
||||
)
|
||||
raise serializers.ValidationError("Must be a valid date in format YYYY-MM-DD or YYYY-MM-DD HH:MM:SS")
|
||||
|
||||
# DECIMAL - must be a number
|
||||
elif property_type == PropertyTypeEnum.DECIMAL:
|
||||
@@ -237,51 +346,33 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
if property_obj.is_multi:
|
||||
if isinstance(value, list):
|
||||
if not value:
|
||||
raise serializers.ValidationError(
|
||||
"List cannot be empty for multi-value property"
|
||||
)
|
||||
raise serializers.ValidationError("List cannot be empty for multi-value property")
|
||||
# Validate each UUID in the list
|
||||
uuid_list = []
|
||||
for v in value:
|
||||
if not isinstance(v, str):
|
||||
raise serializers.ValidationError(
|
||||
"Each value in list must be a string (UUID)"
|
||||
)
|
||||
raise serializers.ValidationError("Each value in list must be a string (UUID)")
|
||||
try:
|
||||
uuid_obj = uuid.UUID(str(v), version=4)
|
||||
uuid_list.append(uuid_obj)
|
||||
except (ValueError, AttributeError):
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid UUID format: {v}"
|
||||
)
|
||||
raise serializers.ValidationError(f"Invalid UUID format: {v}")
|
||||
|
||||
# Check if all options exist for this property
|
||||
existing_count = IssuePropertyOption.objects.filter(
|
||||
property=property_obj, id__in=uuid_list
|
||||
).count()
|
||||
existing_count = IssuePropertyOption.objects.filter(property=property_obj, id__in=uuid_list).count()
|
||||
if existing_count != len(uuid_list):
|
||||
raise serializers.ValidationError(
|
||||
"One or more selected options do not exist for this property"
|
||||
)
|
||||
raise serializers.ValidationError("One or more selected options do not exist for this property")
|
||||
elif isinstance(value, str):
|
||||
# Single value also acceptable for multi-value property
|
||||
try:
|
||||
uuid_obj = uuid.UUID(str(value), version=4)
|
||||
except (ValueError, AttributeError):
|
||||
raise serializers.ValidationError(
|
||||
"Must be a valid UUID for a property option"
|
||||
)
|
||||
raise serializers.ValidationError("Must be a valid UUID for a property option")
|
||||
|
||||
if not IssuePropertyOption.objects.filter(
|
||||
property=property_obj, id=uuid_obj
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
"Selected option does not exist for this property"
|
||||
)
|
||||
if not IssuePropertyOption.objects.filter(property=property_obj, id=uuid_obj).exists():
|
||||
raise serializers.ValidationError("Selected option does not exist for this property")
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
"Must be a string (UUID) or list of UUIDs"
|
||||
)
|
||||
raise serializers.ValidationError("Must be a string (UUID) or list of UUIDs")
|
||||
else:
|
||||
# Single value only
|
||||
if not isinstance(value, str):
|
||||
@@ -289,46 +380,32 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
try:
|
||||
uuid_obj = uuid.UUID(str(value), version=4)
|
||||
except (ValueError, AttributeError):
|
||||
raise serializers.ValidationError(
|
||||
"Must be a valid UUID for a property option"
|
||||
)
|
||||
raise serializers.ValidationError("Must be a valid UUID for a property option")
|
||||
|
||||
if not IssuePropertyOption.objects.filter(
|
||||
property=property_obj, id=uuid_obj
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
"Selected option does not exist for this property"
|
||||
)
|
||||
if not IssuePropertyOption.objects.filter(property=property_obj, id=uuid_obj).exists():
|
||||
raise serializers.ValidationError("Selected option does not exist for this property")
|
||||
|
||||
# RELATION - must be a valid UUID or list of UUIDs (if is_multi)
|
||||
elif property_type == PropertyTypeEnum.RELATION:
|
||||
# Validate based on relation type
|
||||
if not property_obj.relation_type:
|
||||
raise serializers.ValidationError(
|
||||
"Property relation type is not configured"
|
||||
)
|
||||
raise serializers.ValidationError("Property relation type is not configured")
|
||||
|
||||
# Handle multi-value properties
|
||||
if property_obj.is_multi:
|
||||
if isinstance(value, list):
|
||||
if not value:
|
||||
raise serializers.ValidationError(
|
||||
"List cannot be empty for multi-value property"
|
||||
)
|
||||
raise serializers.ValidationError("List cannot be empty for multi-value property")
|
||||
# Validate each UUID in the list
|
||||
uuid_list = []
|
||||
for v in value:
|
||||
if not isinstance(v, str):
|
||||
raise serializers.ValidationError(
|
||||
"Each value in list must be a string (UUID)"
|
||||
)
|
||||
raise serializers.ValidationError("Each value in list must be a string (UUID)")
|
||||
try:
|
||||
uuid_obj = uuid.UUID(str(v), version=4)
|
||||
uuid_list.append(uuid_obj)
|
||||
except (ValueError, AttributeError):
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid UUID format: {v}"
|
||||
)
|
||||
raise serializers.ValidationError(f"Invalid UUID format: {v}")
|
||||
|
||||
# Check if all relations exist
|
||||
if property_obj.relation_type == RelationTypeEnum.ISSUE:
|
||||
@@ -349,9 +426,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
"One or more referenced users are not members of this workspace"
|
||||
)
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid relation type: {property_obj.relation_type}"
|
||||
)
|
||||
raise serializers.ValidationError(f"Invalid relation type: {property_obj.relation_type}")
|
||||
elif isinstance(value, str):
|
||||
# Single value also acceptable for multi-value property
|
||||
try:
|
||||
@@ -360,27 +435,17 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
raise serializers.ValidationError("Must be a valid UUID")
|
||||
|
||||
if property_obj.relation_type == RelationTypeEnum.ISSUE:
|
||||
if not Issue.objects.filter(
|
||||
workspace_id=property_obj.workspace_id, id=uuid_obj
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
"Referenced work item does not exist in this workspace"
|
||||
)
|
||||
if not Issue.objects.filter(workspace_id=property_obj.workspace_id, id=uuid_obj).exists():
|
||||
raise serializers.ValidationError("Referenced work item does not exist in this workspace")
|
||||
elif property_obj.relation_type == RelationTypeEnum.USER:
|
||||
if not WorkspaceMember.objects.filter(
|
||||
workspace_id=property_obj.workspace_id, member_id=uuid_obj
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
"Referenced user is not a member of this workspace"
|
||||
)
|
||||
raise serializers.ValidationError("Referenced user is not a member of this workspace")
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid relation type: {property_obj.relation_type}"
|
||||
)
|
||||
raise serializers.ValidationError(f"Invalid relation type: {property_obj.relation_type}")
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
"Must be a string (UUID) or list of UUIDs"
|
||||
)
|
||||
raise serializers.ValidationError("Must be a string (UUID) or list of UUIDs")
|
||||
else:
|
||||
# Single value only
|
||||
if not isinstance(value, str):
|
||||
@@ -391,29 +456,19 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
raise serializers.ValidationError("Must be a valid UUID")
|
||||
|
||||
if property_obj.relation_type == RelationTypeEnum.ISSUE:
|
||||
if not Issue.objects.filter(
|
||||
workspace_id=property_obj.workspace_id, id=uuid_obj
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
"Referenced work item does not exist in this workspace"
|
||||
)
|
||||
if not Issue.objects.filter(workspace_id=property_obj.workspace_id, id=uuid_obj).exists():
|
||||
raise serializers.ValidationError("Referenced work item does not exist in this workspace")
|
||||
elif property_obj.relation_type == RelationTypeEnum.USER:
|
||||
if not WorkspaceMember.objects.filter(
|
||||
workspace_id=property_obj.workspace_id, member_id=uuid_obj
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
"Referenced user is not a member of this workspace"
|
||||
)
|
||||
raise serializers.ValidationError("Referenced user is not a member of this workspace")
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid relation type: {property_obj.relation_type}"
|
||||
)
|
||||
raise serializers.ValidationError(f"Invalid relation type: {property_obj.relation_type}")
|
||||
|
||||
# Unknown property type
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
f"Unsupported property type: {property_type}"
|
||||
)
|
||||
raise serializers.ValidationError(f"Unsupported property type: {property_type}")
|
||||
|
||||
return value
|
||||
|
||||
@@ -450,9 +505,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
property_obj = self.context.get("property")
|
||||
|
||||
if not all([workspace_id, project_id, issue_id, property_obj]):
|
||||
raise serializers.ValidationError(
|
||||
"Missing required context for creating property value"
|
||||
)
|
||||
raise serializers.ValidationError("Missing required context for creating property value")
|
||||
|
||||
value = validated_data["value"]
|
||||
property_type = property_obj.property_type
|
||||
@@ -468,10 +521,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
}
|
||||
|
||||
# Handle multi-value OPTION and RELATION properties
|
||||
if (
|
||||
property_type in [PropertyTypeEnum.OPTION, PropertyTypeEnum.RELATION]
|
||||
and property_obj.is_multi
|
||||
):
|
||||
if property_type in [PropertyTypeEnum.OPTION, PropertyTypeEnum.RELATION] and property_obj.is_multi:
|
||||
# Normalize to list
|
||||
values = value if isinstance(value, list) else [value]
|
||||
|
||||
@@ -533,10 +583,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
property_obj = instance.property
|
||||
|
||||
# Handle multi-value OPTION and RELATION properties
|
||||
if (
|
||||
property_type in [PropertyTypeEnum.OPTION, PropertyTypeEnum.RELATION]
|
||||
and property_obj.is_multi
|
||||
):
|
||||
if property_type in [PropertyTypeEnum.OPTION, PropertyTypeEnum.RELATION] and property_obj.is_multi:
|
||||
# Normalize to list
|
||||
values = new_value if isinstance(new_value, list) else [new_value]
|
||||
|
||||
@@ -556,9 +603,7 @@ class WorkItemPropertyValueRequestSerializer(serializers.Serializer):
|
||||
"issue_id": instance.issue_id,
|
||||
"property": property_obj,
|
||||
"external_id": validated_data.get("external_id", instance.external_id),
|
||||
"external_source": validated_data.get(
|
||||
"external_source", instance.external_source
|
||||
),
|
||||
"external_source": validated_data.get("external_source", instance.external_source),
|
||||
}
|
||||
|
||||
for v in values:
|
||||
@@ -608,36 +653,32 @@ class WorkItemPropertyValueResponseSerializer(serializers.ModelSerializer):
|
||||
|
||||
property_id = serializers.UUIDField(read_only=True)
|
||||
issue_id = serializers.UUIDField(read_only=True)
|
||||
value = serializers.SerializerMethodField(
|
||||
help_text="The actual value, formatted according to property type"
|
||||
)
|
||||
value_type = serializers.SerializerMethodField(
|
||||
help_text="Type of the value"
|
||||
)
|
||||
value = serializers.SerializerMethodField(help_text="The actual value, formatted according to property type")
|
||||
value_type = serializers.SerializerMethodField(help_text="Type of the value")
|
||||
|
||||
class Meta:
|
||||
model = IssuePropertyValue
|
||||
fields = [
|
||||
'id',
|
||||
'property_id',
|
||||
'issue_id',
|
||||
'value',
|
||||
'value_type',
|
||||
'external_id',
|
||||
'external_source',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
"id",
|
||||
"property_id",
|
||||
"issue_id",
|
||||
"value",
|
||||
"value_type",
|
||||
"external_id",
|
||||
"external_source",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'property_id',
|
||||
'issue_id',
|
||||
'value',
|
||||
'value_type',
|
||||
'external_id',
|
||||
'external_source',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
"id",
|
||||
"property_id",
|
||||
"issue_id",
|
||||
"value",
|
||||
"value_type",
|
||||
"external_id",
|
||||
"external_source",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
def get_value(self, obj):
|
||||
@@ -655,9 +696,7 @@ class WorkItemPropertyValueResponseSerializer(serializers.ModelSerializer):
|
||||
]:
|
||||
return obj.value_text
|
||||
elif property_type == PropertyTypeEnum.DATETIME:
|
||||
return (
|
||||
obj.value_datetime.strftime("%Y-%m-%d") if obj.value_datetime else None
|
||||
)
|
||||
return obj.value_datetime.strftime("%Y-%m-%d") if obj.value_datetime else None
|
||||
elif property_type == PropertyTypeEnum.DECIMAL:
|
||||
return obj.value_decimal
|
||||
elif property_type == PropertyTypeEnum.BOOLEAN:
|
||||
|
||||
@@ -155,7 +155,9 @@ class IssuePropertyListCreateAPIEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
data = request.data
|
||||
issue_property_serializer = self.serializer_class(data=data)
|
||||
issue_property_serializer = self.serializer_class(
|
||||
data=data, context={"issue_type": issue_type, "project": project}
|
||||
)
|
||||
issue_property_serializer.is_valid(raise_exception=True)
|
||||
issue_property_serializer.save(
|
||||
workspace=workspace,
|
||||
@@ -238,7 +240,7 @@ class IssuePropertyDetailAPIEndpoint(BaseAPIView):
|
||||
issue_type_id=type_id,
|
||||
pk=property_id,
|
||||
)
|
||||
serializer = self.serializer_class(issue_property)
|
||||
serializer = self.serializer_class(issue_property, context={"issue_type": type_id, "project": project_id})
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
# update issue property by id
|
||||
@@ -279,7 +281,9 @@ class IssuePropertyDetailAPIEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
data = request.data
|
||||
issue_property_serializer = self.serializer_class(issue_property, data=data, partial=True)
|
||||
issue_property_serializer = self.serializer_class(
|
||||
issue_property, data=data, partial=True, context={"issue_type": type_id, "project": project_id}
|
||||
)
|
||||
issue_property_serializer.is_valid(raise_exception=True)
|
||||
issue_property_serializer.save()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user