ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: additive read-layer двусторонней доски внешних контуров

This commit is contained in:
DCCONSTRUCTIONS
2026-04-20 20:31:02 +03:00
parent f12a3b7338
commit 0184ff9a32
7 changed files with 582 additions and 7 deletions
@@ -54,6 +54,7 @@ from .intake import (
IntakeIssueUpdateSerializer,
)
from .external_contours import (
ExternalContourBoardItemSerializer,
ExternalContourRequestCreateSerializer,
ExternalContourRequestDecisionSerializer,
ExternalContourRequestReplySerializer,
@@ -212,6 +212,10 @@ class ExternalContourRequestSerializer(BaseSerializer):
return obj.extra.get("source_project_id")
def get_has_unread_updates(self, obj):
annotated_value = getattr(obj, "has_unread_updates_annotated", None)
if annotated_value is not None:
return annotated_value
request = self.context.get("request")
user = getattr(request, "user", None)
user_id = getattr(user, "id", None)
@@ -329,3 +333,96 @@ class ExternalContourRequestSerializer(BaseSerializer):
if issue and issue.state and issue.state.group in ["completed", "cancelled"]:
return "closed"
return "open"
class ExternalContourBoardItemSerializer(ExternalContourRequestSerializer):
direction = serializers.SerializerMethodField()
source_project = serializers.SerializerMethodField()
target_project = serializers.SerializerMethodField()
requested_by = serializers.SerializerMethodField()
capabilities = serializers.SerializerMethodField()
class Meta(ExternalContourRequestSerializer.Meta):
fields = ExternalContourRequestSerializer.Meta.fields + [
"direction",
"source_project",
"target_project",
"requested_by",
"capabilities",
]
read_only_fields = fields
def _resolve_direction(self, obj):
current_project_id = str(self.context.get("current_project_id") or "")
source_project_id = str(obj.extra.get("source_project_id") or "")
target_project_id = str(obj.extra.get("target_project_id") or obj.issue.project_id or "")
if current_project_id and current_project_id == source_project_id:
return "outgoing"
if current_project_id and current_project_id == target_project_id:
return "incoming"
return self.context.get("direction") or "outgoing"
def get_has_unread_updates(self, obj):
unread_request_ids = self.context.get("unread_request_ids")
if unread_request_ids is not None:
return str(obj.id) in unread_request_ids
return super().get_has_unread_updates(obj)
def _get_project_payload(self, project_id, fallback_name=None):
if not project_id:
return None
project = (self.context.get("project_map") or {}).get(str(project_id))
return {
"id": str(project_id),
"identifier": getattr(project, "identifier", None),
"name": getattr(project, "name", None) or fallback_name,
"logo_props": getattr(project, "logo_props", None),
}
def get_direction(self, obj):
return self._resolve_direction(obj)
def get_source_project(self, obj):
source_project_id = obj.extra.get("source_project_id")
fallback_name = obj.extra.get("source_project_name")
return self._get_project_payload(source_project_id, fallback_name=fallback_name)
def get_target_project(self, obj):
target_project_id = obj.extra.get("target_project_id") or (str(obj.issue.project_id) if obj.issue_id else None)
fallback_name = obj.extra.get("target_project_name") or (obj.issue.project.name if obj.issue and obj.issue.project else None)
return self._get_project_payload(target_project_id, fallback_name=fallback_name)
def get_requested_by(self, obj):
requested_by_id = obj.extra.get("requested_by_id") or (str(obj.created_by_id) if obj.created_by_id else None)
requested_by_name = obj.extra.get("requested_by_name") or self.get_requested_by_name(obj)
return {
"id": requested_by_id,
"display_name": requested_by_name,
}
def get_capabilities(self, obj):
request = self.context.get("request")
user_id = str(getattr(getattr(request, "user", None), "id", "") or "")
requested_by_id = str(obj.extra.get("requested_by_id") or obj.created_by_id or "")
direction = self._resolve_direction(obj)
issue_project_id = str(obj.issue.project_id) if obj.issue_id and obj.issue and obj.issue.project_id else ""
member_project_ids = self.context.get("member_project_ids") or set()
is_open_request = self.get_status(obj) == "open"
return {
"can_open_detail": True,
"can_open_target_issue": issue_project_id in member_project_ids,
"can_edit_request": direction == "outgoing" and user_id == requested_by_id and is_open_request,
"can_reply": direction == "outgoing" and bool(obj.issue_id),
"can_source_decide": direction == "outgoing" and not is_open_request,
}
def get_source_project_name(self, obj):
source_project = self.get_source_project(obj)
return source_project["name"] if source_project else None
def get_target_project_name(self, obj):
target_project = self.get_target_project(obj)
return target_project["name"] if target_project else None
@@ -6,6 +6,8 @@ from django.urls import path
from plane.app.views import (
ExternalContourAttachmentDownloadEndpoint,
ExternalContourBoardEndpoint,
ExternalContourBoardItemDetailEndpoint,
ExternalContourDetailEndpoint,
ExternalContourDecisionEndpoint,
ExternalContourListCreateEndpoint,
@@ -16,6 +18,16 @@ from plane.app.views import (
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/external-contours/board/",
ExternalContourBoardEndpoint.as_view(http_method_names=["get"]),
name="external-contour-board",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/external-contours/board-items/<uuid:request_id>/",
ExternalContourBoardItemDetailEndpoint.as_view(http_method_names=["get"]),
name="external-contour-board-item-detail",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/external-contours/",
ExternalContourListCreateEndpoint.as_view(http_method_names=["get", "post"]),