# Third party imports from rest_framework import serializers # Module imports from .base import BaseSerializer, DynamicBaseSerializer from plane.app.serializers.workspace import WorkspaceLiteSerializer from plane.app.serializers.user import UserLiteSerializer, UserAdminLiteSerializer from plane.db.models import ( Project, ProjectMember, ProjectMemberInvite, ProjectIdentifier, DeployBoard, ProjectPublicMember, ) from plane.utils.content_validator import ( validate_html_content, validate_json_content, validate_binary_data, ) class ProjectSerializer(BaseSerializer): workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True) inbox_view = serializers.BooleanField(read_only=True, source="intake_view") class Meta: model = Project fields = "__all__" read_only_fields = ["workspace", "deleted_at"] def validate_name(self, name): project_id = self.instance.id if self.instance else None workspace_id = self.context["workspace_id"] project = Project.objects.filter(name=name, workspace_id=workspace_id) if project_id: project = project.exclude(id=project_id) if project.exists(): raise serializers.ValidationError( detail="PROJECT_NAME_ALREADY_EXIST", ) return name def validate_identifier(self, identifier): project_id = self.instance.id if self.instance else None workspace_id = self.context["workspace_id"] project = Project.objects.filter( identifier=identifier, workspace_id=workspace_id ) if project_id: project = project.exclude(id=project_id) if project.exists(): raise serializers.ValidationError( detail="PROJECT_IDENTIFIER_ALREADY_EXIST", ) return identifier def validate(self, data): # Validate description content for security if "description" in data and data["description"]: # For Project, description might be text field, not JSON if isinstance(data["description"], dict): is_valid, error_msg = validate_json_content(data["description"]) if not is_valid: raise serializers.ValidationError({"description": error_msg}) if "description_text" in data and data["description_text"]: is_valid, error_msg = validate_json_content(data["description_text"]) if not is_valid: raise serializers.ValidationError({"description_text": error_msg}) if "description_html" in data and data["description_html"]: if isinstance(data["description_html"], dict): is_valid, error_msg = validate_json_content(data["description_html"]) else: is_valid, error_msg = validate_html_content( str(data["description_html"]) ) if not is_valid: raise serializers.ValidationError({"description_html": error_msg}) return data def create(self, validated_data): workspace_id = self.context["workspace_id"] project = Project.objects.create(**validated_data, workspace_id=workspace_id) ProjectIdentifier.objects.create( name=project.identifier, project=project, workspace_id=workspace_id ) return project class ProjectLiteSerializer(BaseSerializer): class Meta: model = Project fields = [ "id", "identifier", "name", "cover_image", "cover_image_url", "logo_props", "description", ] read_only_fields = fields class ProjectListSerializer(DynamicBaseSerializer): is_favorite = serializers.BooleanField(read_only=True) sort_order = serializers.FloatField(read_only=True) member_role = serializers.IntegerField(read_only=True) anchor = serializers.CharField(read_only=True) members = serializers.SerializerMethodField() cover_image_url = serializers.CharField(read_only=True) inbox_view = serializers.BooleanField(read_only=True, source="intake_view") def get_members(self, obj): project_members = getattr(obj, "members_list", None) if project_members is not None: # Filter members by the project ID return [ member.member_id for member in project_members if member.is_active and not member.member.is_bot ] return [] class Meta: model = Project fields = "__all__" class ProjectDetailSerializer(BaseSerializer): # workspace = WorkSpaceSerializer(read_only=True) default_assignee = UserLiteSerializer(read_only=True) project_lead = UserLiteSerializer(read_only=True) is_favorite = serializers.BooleanField(read_only=True) sort_order = serializers.FloatField(read_only=True) member_role = serializers.IntegerField(read_only=True) anchor = serializers.CharField(read_only=True) class Meta: model = Project fields = "__all__" class ProjectMemberSerializer(BaseSerializer): workspace = WorkspaceLiteSerializer(read_only=True) project = ProjectLiteSerializer(read_only=True) member = UserLiteSerializer(read_only=True) class Meta: model = ProjectMember fields = "__all__" class ProjectMemberAdminSerializer(BaseSerializer): workspace = WorkspaceLiteSerializer(read_only=True) project = ProjectLiteSerializer(read_only=True) member = UserAdminLiteSerializer(read_only=True) class Meta: model = ProjectMember fields = "__all__" class ProjectMemberRoleSerializer(DynamicBaseSerializer): original_role = serializers.IntegerField(source="role", read_only=True) class Meta: model = ProjectMember fields = ("id", "role", "member", "project", "original_role", "created_at") read_only_fields = ["original_role", "created_at"] class ProjectMemberInviteSerializer(BaseSerializer): project = ProjectLiteSerializer(read_only=True) workspace = WorkspaceLiteSerializer(read_only=True) class Meta: model = ProjectMemberInvite fields = "__all__" class ProjectIdentifierSerializer(BaseSerializer): class Meta: model = ProjectIdentifier fields = "__all__" class ProjectMemberLiteSerializer(BaseSerializer): member = UserLiteSerializer(read_only=True) is_subscribed = serializers.BooleanField(read_only=True) class Meta: model = ProjectMember fields = ["member", "id", "is_subscribed"] read_only_fields = fields class DeployBoardSerializer(BaseSerializer): project_details = ProjectLiteSerializer(read_only=True, source="project") workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") class Meta: model = DeployBoard fields = "__all__" read_only_fields = ["workspace", "project", "anchor"] class ProjectPublicMemberSerializer(BaseSerializer): class Meta: model = ProjectPublicMember fields = "__all__" read_only_fields = ["workspace", "project", "member"]