ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: интерактивные карточки и фильтры двусторонней доски внешних контуров

This commit is contained in:
DCCONSTRUCTIONS
2026-04-20 22:57:18 +03:00
parent 6a3adcd245
commit d3b4c0689c
16 changed files with 754 additions and 224 deletions
@@ -44,6 +44,11 @@ class ExternalContourRequestReplySerializer(serializers.Serializer):
class ExternalContourRequestUpdateSerializer(serializers.Serializer):
name = serializers.CharField(max_length=255, required=False)
description_html = serializers.CharField(required=False, allow_blank=True, allow_null=True)
priority = serializers.ChoiceField(choices=Issue.PRIORITY_CHOICES, required=False)
assignee_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
label_ids = serializers.ListField(child=serializers.UUIDField(), required=False)
state_id = serializers.UUIDField(required=False)
target_date = serializers.DateField(required=False, allow_null=True)
def validate(self, data):
if not data:
@@ -69,6 +74,7 @@ class ExternalContourTargetProjectSerializer(BaseSerializer):
class ExternalContourTargetOptionsSerializer(serializers.Serializer):
project = ExternalContourTargetProjectSerializer(read_only=True)
member_ids = serializers.ListField(child=serializers.UUIDField(), read_only=True)
states = StateLiteSerializer(many=True, read_only=True)
labels = LabelSerializer(many=True, read_only=True)
@@ -247,11 +247,15 @@ class ExternalContourTargetOptionsEndpoint(BaseAPIView):
)
labels = Label.objects.filter(project=target_project).order_by("sort_order", "name")
states = State.objects.filter(project=target_project).exclude(group=StateGroup.TRIAGE.value).order_by(
"sequence", "created_at"
)
serializer = ExternalContourTargetOptionsSerializer(
{
"project": target_project,
"member_ids": member_ids,
"states": states,
"labels": labels,
}
)
@@ -326,10 +330,17 @@ class ExternalContourDetailEndpoint(BaseAPIView):
serializer = ExternalContourRequestUpdateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
issue_update_data = serializer.validated_data.copy()
assignee_ids = issue_update_data.pop("assignee_ids", None)
label_ids = issue_update_data.pop("label_ids", None)
if assignee_ids is not None:
issue_update_data["assignees"] = assignee_ids
if label_ids is not None:
issue_update_data["labels"] = label_ids
issue_serializer = IssueCreateSerializer(
issue,
data=serializer.validated_data,
data=issue_update_data,
partial=True,
context={
"project_id": str(issue.project_id),
@@ -249,11 +249,15 @@ class ExternalContourTargetOptionsEndpoint(BaseAPIView):
)
labels = Label.objects.filter(project=target_project).order_by("sort_order", "name")
states = State.objects.filter(project=target_project).exclude(group=StateGroup.TRIAGE.value).order_by(
"sequence", "created_at"
)
serializer = ExternalContourTargetOptionsSerializer(
{
"project": target_project,
"member_ids": member_ids,
"states": states,
"labels": labels,
}
)
@@ -324,6 +328,7 @@ class ExternalContourReadMixin:
"assignee_ids",
"created_by_ids",
"requested_by_ids",
"counterparty_project_ids",
"source_project_ids",
"target_project_ids",
"label_ids",
@@ -377,7 +382,7 @@ class ExternalContourReadMixin:
return queryset.filter(issue__state__group__in=self.CLOSED_STATE_GROUPS)
return queryset
def apply_board_filters(self, queryset, request):
def apply_board_filters(self, queryset, request, direction=None):
filters = self.get_applied_filters(request)
if filters["search"]:
@@ -397,6 +402,14 @@ class ExternalContourReadMixin:
queryset = queryset.filter(issue__created_by_id__in=filters["created_by_ids"])
if filters["requested_by_ids"]:
queryset = queryset.filter(extra__requested_by_id__in=filters["requested_by_ids"])
if filters["counterparty_project_ids"]:
if direction == "outgoing":
queryset = queryset.filter(
Q(extra__target_project_id__in=filters["counterparty_project_ids"])
| Q(issue__project_id__in=filters["counterparty_project_ids"])
)
elif direction == "incoming":
queryset = queryset.filter(extra__source_project_id__in=filters["counterparty_project_ids"])
if filters["source_project_ids"]:
queryset = queryset.filter(extra__source_project_id__in=filters["source_project_ids"])
if filters["target_project_ids"]:
@@ -494,8 +507,8 @@ class ExternalContourBoardEndpoint(ExternalContourReadMixin, BaseAPIView):
requested_directions = self.get_requested_directions(request)
current_project_id = str(project_id)
outgoing_queryset = self.apply_board_filters(self.get_outgoing_queryset(), request)
incoming_queryset = self.apply_board_filters(self.get_incoming_queryset(), request)
outgoing_queryset = self.apply_board_filters(self.get_outgoing_queryset(), request, direction="outgoing")
incoming_queryset = self.apply_board_filters(self.get_incoming_queryset(), request, direction="incoming")
outgoing_requests = list(outgoing_queryset) if "outgoing" in requested_directions else []
incoming_requests = list(incoming_queryset) if "incoming" in requested_directions else []
@@ -624,10 +637,17 @@ class ExternalContourDetailEndpoint(BaseAPIView):
serializer = ExternalContourRequestUpdateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
issue_update_data = serializer.validated_data.copy()
assignee_ids = issue_update_data.pop("assignee_ids", None)
label_ids = issue_update_data.pop("label_ids", None)
if assignee_ids is not None:
issue_update_data["assignees"] = assignee_ids
if label_ids is not None:
issue_update_data["labels"] = label_ids
issue_serializer = IssueCreateSerializer(
issue,
data=serializer.validated_data,
data=issue_update_data,
partial=True,
context={
"project_id": str(issue.project_id),