407 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.db.models import Max
from django.contrib.auth import get_user_model
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from api.v1.employees.serializers import EmployeeBriefSerializer
from communications.comments_helpers import get_comment_count
from tasks.access import (
user_can_access_task_board,
)
from tasks.models import (
Task,
TaskCover,
TaskCoverType,
TaskLabel,
TaskLinkedObjectKind,
)
from .linked import TaskCoverSerializer
from .structure import TaskLabelSerializer
User = get_user_model()
class TaskSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
assignee = EmployeeBriefSerializer(read_only=True)
participants = EmployeeBriefSerializer(many=True, read_only=True)
participant_ids = serializers.PrimaryKeyRelatedField(
source="participants",
queryset=User.objects.filter(is_active=True),
many=True,
required=False,
write_only=True,
)
assignee_id = serializers.PrimaryKeyRelatedField(
source="assignee",
queryset=User.objects.all(),
required=False,
allow_null=True,
write_only=True,
)
labels = TaskLabelSerializer(many=True, read_only=True)
label_ids = serializers.PrimaryKeyRelatedField(
source="labels",
queryset=TaskLabel.objects.all(),
many=True,
required=False,
write_only=True,
)
priority_display = serializers.CharField(
source="get_priority_display",
read_only=True,
)
board_name = serializers.CharField(source="board.name", read_only=True)
column_name = serializers.CharField(source="column.name", read_only=True)
column_color = serializers.CharField(source="column.color", read_only=True)
row_name = serializers.CharField(source="row.name", read_only=True)
row_color = serializers.CharField(source="row.color", read_only=True)
linked_posts_count = serializers.SerializerMethodField()
linked_messages_count = serializers.SerializerMethodField()
linked_events_count = serializers.SerializerMethodField()
linked_documents_count = serializers.SerializerMethodField()
linked_requests_count = serializers.SerializerMethodField()
linked_procurement_requests_count = serializers.SerializerMethodField()
linked_employees_count = serializers.SerializerMethodField()
linked_guests_count = serializers.SerializerMethodField()
linked_guest_visits_count = serializers.SerializerMethodField()
linked_attendance_records_count = serializers.SerializerMethodField()
linked_objects_count = serializers.SerializerMethodField()
comments_count = serializers.SerializerMethodField()
attachments_count = serializers.SerializerMethodField()
cover = serializers.SerializerMethodField()
cover_attachment = serializers.SerializerMethodField()
checklist_total = serializers.SerializerMethodField()
checklist_completed = serializers.SerializerMethodField()
class Meta:
model = Task
fields = [
"id",
"board",
"board_name",
"column",
"column_name",
"column_color",
"row",
"row_name",
"row_color",
"title",
"description",
"created_by",
"assignee",
"assignee_id",
"participants",
"participant_ids",
"labels",
"label_ids",
"priority",
"priority_display",
"due_date",
"position",
"completed_at",
"linked_posts_count",
"linked_messages_count",
"linked_events_count",
"linked_documents_count",
"linked_requests_count",
"linked_procurement_requests_count",
"linked_employees_count",
"linked_guests_count",
"linked_guest_visits_count",
"linked_attendance_records_count",
"linked_objects_count",
"comments_count",
"attachments_count",
"cover",
"cover_attachment",
"checklist_total",
"checklist_completed",
"created_at",
"updated_at",
]
read_only_fields = [
"id",
"created_by",
"participants",
"board_name",
"column_name",
"column_color",
"row_name",
"row_color",
"priority_display",
"completed_at",
"linked_posts_count",
"linked_messages_count",
"linked_events_count",
"linked_documents_count",
"linked_requests_count",
"linked_procurement_requests_count",
"linked_employees_count",
"linked_guests_count",
"linked_guest_visits_count",
"linked_attendance_records_count",
"linked_objects_count",
"comments_count",
"attachments_count",
"cover",
"cover_attachment",
"checklist_total",
"checklist_completed",
"created_at",
"updated_at",
]
@extend_schema_field(serializers.IntegerField())
def get_linked_posts_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_posts_count",
TaskLinkedObjectKind.POST,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_messages_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_messages_count",
TaskLinkedObjectKind.MESSAGE,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_events_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_events_count",
TaskLinkedObjectKind.CALENDAR_EVENT,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_documents_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_documents_count",
TaskLinkedObjectKind.DOCUMENT,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_requests_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_requests_count",
TaskLinkedObjectKind.REQUEST,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_procurement_requests_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_procurement_requests_count",
TaskLinkedObjectKind.PROCUREMENT_REQUEST,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_employees_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_employees_count",
TaskLinkedObjectKind.EMPLOYEE,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_guests_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_guests_count",
TaskLinkedObjectKind.GUEST,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_guest_visits_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_guest_visits_count",
TaskLinkedObjectKind.GUEST_VISIT,
)
@extend_schema_field(serializers.IntegerField())
def get_linked_attendance_records_count(self, obj):
return self._get_linked_kind_count(
obj,
"linked_attendance_records_count",
TaskLinkedObjectKind.ATTENDANCE_RECORD,
)
def _get_linked_kind_count(self, obj, attribute_name, kind):
if hasattr(obj, attribute_name):
return getattr(obj, attribute_name)
prefetched = getattr(obj, "_prefetched_objects_cache", {}).get("linked_objects")
if prefetched is not None:
return sum(link.kind == kind for link in prefetched)
return obj.linked_objects.filter(kind=kind).count()
@extend_schema_field(serializers.IntegerField())
def get_linked_objects_count(self, obj):
if hasattr(obj, "linked_objects_count"):
return obj.linked_objects_count
return obj.linked_objects.count() + obj.external_links.count()
@extend_schema_field(serializers.IntegerField())
def get_comments_count(self, obj):
if hasattr(obj, "comments_count"):
return obj.comments_count
return get_comment_count(obj)
@extend_schema_field(serializers.IntegerField())
def get_attachments_count(self, obj):
if hasattr(obj, "attachments_count"):
return obj.attachments_count
return obj.attachments.count()
def _get_cover_payload(self, obj):
if hasattr(obj, "_task_cover_payload"):
return obj._task_cover_payload
try:
cover = obj.cover
except TaskCover.DoesNotExist:
cover = None
if (
cover is not None
and cover.kind == TaskCoverType.CHECKLIST
and not obj.checklist_items.exists()
):
cover = None
payload = (
TaskCoverSerializer(cover, context=self.context).data
if cover is not None
else None
)
obj._task_cover_payload = payload
return payload
@extend_schema_field(serializers.DictField(allow_null=True))
def get_cover(self, obj):
return self._get_cover_payload(obj)
@extend_schema_field(serializers.DictField(allow_null=True))
def get_cover_attachment(self, obj):
payload = self._get_cover_payload(obj)
return payload.get("attachment") if payload else None
@extend_schema_field(serializers.IntegerField())
def get_checklist_total(self, obj):
if hasattr(obj, "checklist_total"):
return obj.checklist_total
return obj.checklist_items.count()
@extend_schema_field(serializers.IntegerField())
def get_checklist_completed(self, obj):
if hasattr(obj, "checklist_completed"):
return obj.checklist_completed
prefetched = getattr(obj, "_prefetched_objects_cache", {}).get(
"checklist_items"
)
if prefetched is not None:
return sum(item.is_completed for item in prefetched)
return obj.checklist_items.filter(is_completed=True).count()
def validate(self, attrs):
request = self.context.get("request")
user = getattr(request, "user", None)
board = attrs.get("board") or getattr(self.instance, "board", None)
column = attrs.get("column") or getattr(self.instance, "column", None)
row = (
attrs.get("row") if "row" in attrs else getattr(self.instance, "row", None)
)
participants = attrs.get("participants")
if board and not user_can_access_task_board(user, board):
raise serializers.ValidationError(
{"board": "У вас нет доступа к этой доске."}
)
if column and board and column.board_id != board.id:
raise serializers.ValidationError(
{"column": "Колонка должна принадлежать выбранной доске."}
)
if column and column.subcolumns.filter(is_archived=False).exists():
raise serializers.ValidationError(
{"column": "Выберите подколонку для размещения задачи."}
)
if row and column:
top_level_column_id = column.parent_id or column.id
if row.column_id != top_level_column_id:
raise serializers.ValidationError(
{"row": "Дорожка должна принадлежать основной колонке задачи."}
)
if participants is not None and board:
inaccessible_participants = [
participant.id
for participant in participants
if not user_can_access_task_board(participant, board)
]
if inaccessible_participants:
raise serializers.ValidationError(
{
"participant_ids": (
"Участниками могут быть только сотрудники с доступом "
"к этой доске."
)
}
)
return attrs
def create(self, validated_data):
labels = validated_data.pop("labels", [])
participants = list(validated_data.pop("participants", []))
if "position" not in validated_data:
column = validated_data["column"]
row = validated_data.get("row")
max_position = (
Task.objects.filter(column=column, row=row).aggregate(Max("position"))[
"position__max"
]
or 0
)
validated_data["position"] = max_position + 1000
task = Task.objects.create(**validated_data)
participant_ids = {participant.id for participant in participants}
participant_ids.add(task.created_by_id)
if task.assignee_id:
participant_ids.add(task.assignee_id)
task.participants.set(participant_ids)
if labels:
task.labels.set(labels)
return task
def update(self, instance, validated_data):
previous_assignee_id = instance.assignee_id
labels = validated_data.pop("labels", None)
participants = validated_data.pop("participants", None)
task = super().update(instance, validated_data)
if participants is not None:
participant_ids = {participant.id for participant in participants}
participant_ids.add(task.created_by_id)
if previous_assignee_id:
participant_ids.add(previous_assignee_id)
if task.assignee_id:
participant_ids.add(task.assignee_id)
task.participants.set(participant_ids)
else:
retained_participant_ids = [task.assignee_id, previous_assignee_id]
task.participants.add(
*[
participant_id
for participant_id in retained_participant_ids
if participant_id
]
)
if labels is not None:
task.labels.set(labels)
return task