Corp/backend/api/v1/tasks/serializers.py

711 lines
24 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 django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from api.v1.documents.serializers import DocumentReadSerializer
from api.v1.employees.serializers import EmployeeBriefSerializer
from api.v1.employees.serializers.department import DepartmentBriefSerializer
from communications.models import Message
from communications.comments_helpers import get_comment_count
from communications.serialization import serialize_message
from communications.utils import user_can_access_chat
from documents.models import Document
from employees.models import Department
from procurement.models import ProcurementRequest
from requests_app.models import Request as EmployeeRequest
from schedule.models import Event
from api.v1.schedule.serializers import EventSerializer
from tasks.access import (
user_can_access_calendar_event,
user_can_access_document,
user_can_access_employee_request,
user_can_access_procurement_request,
user_can_access_task_board,
)
from tasks.models import (
Task,
TaskActivity,
TaskBoard,
TaskColumn,
TaskLabel,
TaskLinkedObject,
TaskLinkedObjectKind,
)
User = get_user_model()
DEFAULT_COLUMNS = [
("Новые", "#38bdf8", False),
("В работе", "#f59e0b", False),
("На проверке", "#8b5cf6", False),
("Готово", "#22c55e", True),
]
def create_default_columns(board):
if board.columns.exists():
return
TaskColumn.objects.bulk_create(
[
TaskColumn(
board=board,
name=name,
color=color,
is_done=is_done,
position=index * 1000,
)
for index, (name, color, is_done) in enumerate(DEFAULT_COLUMNS)
]
)
def validate_board_access(serializer, board):
request = serializer.context.get("request")
user = getattr(request, "user", None)
if not user_can_access_task_board(user, board):
raise serializers.ValidationError("У вас нет доступа к этой доске.")
return board
class TaskLabelSerializer(serializers.ModelSerializer):
class Meta:
model = TaskLabel
fields = ["id", "board", "name", "color"]
read_only_fields = ["id"]
def validate_board(self, board):
return validate_board_access(self, board)
class TaskColumnSerializer(serializers.ModelSerializer):
tasks_count = serializers.IntegerField(read_only=True, default=0)
class Meta:
model = TaskColumn
fields = [
"id",
"board",
"name",
"position",
"color",
"is_done",
"is_archived",
"tasks_count",
"created_at",
"updated_at",
]
read_only_fields = [
"id",
"created_by",
"member_details",
"department_details",
"columns",
"labels",
"tasks",
"tasks_count",
"created_at",
"updated_at",
]
read_only_fields = ["id", "created_at", "updated_at", "tasks_count"]
def validate_board(self, board):
return validate_board_access(self, board)
class TaskSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
assignee = EmployeeBriefSerializer(read_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)
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_objects_count = serializers.SerializerMethodField()
comments_count = serializers.SerializerMethodField()
class Meta:
model = Task
fields = [
"id",
"board",
"board_name",
"column",
"column_name",
"column_color",
"title",
"description",
"created_by",
"assignee",
"assignee_id",
"labels",
"label_ids",
"priority",
"priority_display",
"due_date",
"position",
"completed_at",
"linked_messages_count",
"linked_events_count",
"linked_documents_count",
"linked_requests_count",
"linked_procurement_requests_count",
"linked_objects_count",
"comments_count",
"created_at",
"updated_at",
]
read_only_fields = [
"id",
"created_by",
"board_name",
"column_name",
"column_color",
"priority_display",
"completed_at",
"linked_messages_count",
"linked_events_count",
"linked_documents_count",
"linked_requests_count",
"linked_procurement_requests_count",
"linked_objects_count",
"comments_count",
"created_at",
"updated_at",
]
@extend_schema_field(serializers.IntegerField())
def get_linked_messages_count(self, obj):
if hasattr(obj, "linked_messages_count"):
return obj.linked_messages_count
return obj.linked_objects.filter(
kind=TaskLinkedObjectKind.MESSAGE,
).count()
@extend_schema_field(serializers.IntegerField())
def get_linked_events_count(self, obj):
if hasattr(obj, "linked_events_count"):
return obj.linked_events_count
return obj.linked_objects.filter(
kind=TaskLinkedObjectKind.CALENDAR_EVENT,
).count()
@extend_schema_field(serializers.IntegerField())
def get_linked_documents_count(self, obj):
if hasattr(obj, "linked_documents_count"):
return obj.linked_documents_count
return obj.linked_objects.filter(
kind=TaskLinkedObjectKind.DOCUMENT,
).count()
@extend_schema_field(serializers.IntegerField())
def get_linked_requests_count(self, obj):
if hasattr(obj, "linked_requests_count"):
return obj.linked_requests_count
return obj.linked_objects.filter(
kind=TaskLinkedObjectKind.REQUEST,
).count()
@extend_schema_field(serializers.IntegerField())
def get_linked_procurement_requests_count(self, obj):
if hasattr(obj, "linked_procurement_requests_count"):
return obj.linked_procurement_requests_count
return obj.linked_objects.filter(
kind=TaskLinkedObjectKind.PROCUREMENT_REQUEST,
).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()
@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)
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)
labels = attrs.get("labels")
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 labels is not None and board:
invalid_labels = [
label.id for label in labels if label.board_id != board.id
]
if invalid_labels:
raise serializers.ValidationError(
{"label_ids": "Метки должны принадлежать выбранной доске."}
)
return attrs
def create(self, validated_data):
labels = validated_data.pop("labels", [])
if "position" not in validated_data:
column = validated_data["column"]
max_position = (
Task.objects.filter(column=column).aggregate(Max("position"))[
"position__max"
]
or 0
)
validated_data["position"] = max_position + 1000
task = Task.objects.create(**validated_data)
if labels:
task.labels.set(labels)
return task
def update(self, instance, validated_data):
labels = validated_data.pop("labels", None)
task = super().update(instance, validated_data)
if labels is not None:
task.labels.set(labels)
return task
class TaskBoardSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
members = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(),
many=True,
required=False,
)
member_details = EmployeeBriefSerializer(
source="members",
many=True,
read_only=True,
)
departments = serializers.PrimaryKeyRelatedField(
queryset=Department.objects.all(),
many=True,
required=False,
)
department_details = DepartmentBriefSerializer(
source="departments",
many=True,
read_only=True,
)
columns = TaskColumnSerializer(many=True, read_only=True)
labels = TaskLabelSerializer(many=True, read_only=True)
tasks = TaskSerializer(many=True, read_only=True)
tasks_count = serializers.IntegerField(read_only=True, default=0)
class Meta:
model = TaskBoard
fields = [
"id",
"name",
"description",
"created_by",
"members",
"member_details",
"departments",
"department_details",
"is_archived",
"columns",
"labels",
"tasks",
"tasks_count",
"created_at",
"updated_at",
]
class TaskLinkedMessageSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
message = serializers.SerializerMethodField()
message_id = serializers.IntegerField(source="object_id", read_only=True)
can_open = serializers.SerializerMethodField()
object_url = serializers.SerializerMethodField()
class Meta:
model = TaskLinkedObject
fields = [
"id",
"kind",
"message_id",
"message",
"can_open",
"object_url",
"created_by",
"created_at",
]
read_only_fields = fields
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_message(self, obj):
if obj.kind != TaskLinkedObjectKind.MESSAGE:
return None
message = getattr(obj, "content_object", None)
if message is None:
return None
return serialize_message(message, include_linked_tasks=False)
@extend_schema_field(serializers.BooleanField())
def get_can_open(self, obj):
message = getattr(obj, "content_object", None)
request = self.context.get("request")
user = getattr(request, "user", None)
if obj.kind != TaskLinkedObjectKind.MESSAGE or message is None or not user:
return False
return user_can_access_chat(message.chat, user)
@extend_schema_field(serializers.CharField(allow_null=True))
def get_object_url(self, obj):
message = getattr(obj, "content_object", None)
if message is None or not self.get_can_open(obj):
return None
return f"/messages/{message.chat_id}?message={message.id}"
def get_message_content_type():
return ContentType.objects.get_for_model(Message)
class TaskActivitySerializer(serializers.ModelSerializer):
actor = EmployeeBriefSerializer(read_only=True)
action_display = serializers.CharField(source="get_action_display", read_only=True)
class Meta:
model = TaskActivity
fields = [
"id",
"action",
"action_display",
"actor",
"object_kind",
"object_id",
"metadata",
"created_at",
]
read_only_fields = fields
class TaskLinkedCalendarEventSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
event = serializers.SerializerMethodField()
event_id = serializers.IntegerField(source="object_id", read_only=True)
can_open = serializers.SerializerMethodField()
object_url = serializers.SerializerMethodField()
class Meta:
model = TaskLinkedObject
fields = [
"id",
"kind",
"event_id",
"event",
"can_open",
"object_url",
"created_by",
"created_at",
]
read_only_fields = fields
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_event(self, obj):
if obj.kind != TaskLinkedObjectKind.CALENDAR_EVENT:
return None
event = getattr(obj, "content_object", None)
if event is None:
return None
return EventSerializer(event, context=self.context).data
@extend_schema_field(serializers.BooleanField())
def get_can_open(self, obj):
event = getattr(obj, "content_object", None)
request = self.context.get("request")
user = getattr(request, "user", None)
if (
obj.kind != TaskLinkedObjectKind.CALENDAR_EVENT
or event is None
or not user
):
return False
return user_can_access_calendar_event(user, event)
@extend_schema_field(serializers.CharField(allow_null=True))
def get_object_url(self, obj):
event = getattr(obj, "content_object", None)
if event is None or not self.get_can_open(obj):
return None
return f"/calendar?calendar={event.calendar_id}&event={event.id}"
def get_calendar_event_content_type():
return ContentType.objects.get_for_model(Event)
class TaskLinkedDocumentSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
document = serializers.SerializerMethodField()
document_id = serializers.IntegerField(source="object_id", read_only=True)
can_open = serializers.SerializerMethodField()
object_url = serializers.SerializerMethodField()
class Meta:
model = TaskLinkedObject
fields = [
"id",
"kind",
"document_id",
"document",
"can_open",
"object_url",
"created_by",
"created_at",
]
read_only_fields = fields
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_document(self, obj):
if obj.kind != TaskLinkedObjectKind.DOCUMENT:
return None
document = getattr(obj, "content_object", None)
if document is None:
return None
if not self.get_can_open(obj):
return {
"id": document.id,
"title": document.title,
"description": document.description,
"is_regulation": document.is_regulation,
"folder_path": document.folder_path,
"file_name": (
getattr(document.file, "name", None)
if document.file
else None
),
}
return DocumentReadSerializer(document, context=self.context).data
@extend_schema_field(serializers.BooleanField())
def get_can_open(self, obj):
document = getattr(obj, "content_object", None)
request = self.context.get("request")
user = getattr(request, "user", None)
if (
obj.kind != TaskLinkedObjectKind.DOCUMENT
or document is None
or not user
):
return False
return user_can_access_document(user, document)
@extend_schema_field(serializers.CharField(allow_null=True))
def get_object_url(self, obj):
document = getattr(obj, "content_object", None)
if document is None or not self.get_can_open(obj):
return None
return f"/documents?document={document.id}"
def get_document_content_type():
return ContentType.objects.get_for_model(Document)
class TaskLinkedRequestSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
request = serializers.SerializerMethodField()
request_id = serializers.IntegerField(source="object_id", read_only=True)
can_open = serializers.SerializerMethodField()
object_url = serializers.SerializerMethodField()
class Meta:
model = TaskLinkedObject
fields = [
"id",
"kind",
"request_id",
"request",
"can_open",
"object_url",
"created_by",
"created_at",
]
read_only_fields = fields
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_request(self, obj):
if obj.kind != TaskLinkedObjectKind.REQUEST:
return None
request_obj = getattr(obj, "content_object", None)
if request_obj is None:
return None
return {
"id": request_obj.id,
"title": request_obj.title,
"display_title": request_obj.display_title,
"type": request_obj.type,
"type_display": request_obj.get_type_display(),
"status": request_obj.status,
"status_display": request_obj.get_status_display(),
"comment": request_obj.comment,
"date_from": request_obj.date_from,
"date_to": request_obj.date_to,
"employee": EmployeeBriefSerializer(request_obj.employee).data,
"created_at": request_obj.created_at,
"updated_at": request_obj.updated_at,
}
@extend_schema_field(serializers.BooleanField())
def get_can_open(self, obj):
request_obj = getattr(obj, "content_object", None)
request = self.context.get("request")
user = getattr(request, "user", None)
if (
obj.kind != TaskLinkedObjectKind.REQUEST
or request_obj is None
or not user
):
return False
return user_can_access_employee_request(user, request_obj)
@extend_schema_field(serializers.CharField(allow_null=True))
def get_object_url(self, obj):
request_obj = getattr(obj, "content_object", None)
if request_obj is None or not self.get_can_open(obj):
return None
return f"/requests?request={request_obj.id}"
def get_request_content_type():
return ContentType.objects.get_for_model(EmployeeRequest)
class TaskLinkedProcurementRequestSerializer(serializers.ModelSerializer):
created_by = EmployeeBriefSerializer(read_only=True)
procurement_request = serializers.SerializerMethodField()
procurement_request_id = serializers.IntegerField(source="object_id", read_only=True)
can_open = serializers.SerializerMethodField()
object_url = serializers.SerializerMethodField()
class Meta:
model = TaskLinkedObject
fields = [
"id",
"kind",
"procurement_request_id",
"procurement_request",
"can_open",
"object_url",
"created_by",
"created_at",
]
read_only_fields = fields
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_procurement_request(self, obj):
if obj.kind != TaskLinkedObjectKind.PROCUREMENT_REQUEST:
return None
procurement_request = getattr(obj, "content_object", None)
if procurement_request is None:
return None
return {
"id": procurement_request.id,
"title": procurement_request.title,
"description": procurement_request.description,
"status": procurement_request.status,
"status_display": procurement_request.get_status_display(),
"urgency": procurement_request.urgency,
"urgency_display": procurement_request.get_urgency_display(),
"fulfillment_status": procurement_request.fulfillment_status,
"fulfillment_status_display": procurement_request.get_fulfillment_status_display(),
"department_id": procurement_request.department_id,
"department_name": (
procurement_request.department.name
if procurement_request.department_id
else None
),
"processing_department_id": procurement_request.processing_department_id,
"processing_department_name": (
procurement_request.processing_department.name
if procurement_request.processing_department_id
else None
),
"requestor": EmployeeBriefSerializer(procurement_request.requestor).data,
"executor": (
EmployeeBriefSerializer(procurement_request.executor).data
if procurement_request.executor_id
else None
),
"total_cost": procurement_request.total_cost,
"items_count": procurement_request.items_count,
"created_at": procurement_request.created_at,
"updated_at": procurement_request.updated_at,
}
@extend_schema_field(serializers.BooleanField())
def get_can_open(self, obj):
procurement_request = getattr(obj, "content_object", None)
request = self.context.get("request")
user = getattr(request, "user", None)
if (
obj.kind != TaskLinkedObjectKind.PROCUREMENT_REQUEST
or procurement_request is None
or not user
):
return False
return user_can_access_procurement_request(user, procurement_request)
@extend_schema_field(serializers.CharField(allow_null=True))
def get_object_url(self, obj):
procurement_request = getattr(obj, "content_object", None)
if procurement_request is None or not self.get_can_open(obj):
return None
return f"/procurement?request={procurement_request.id}"
def get_procurement_request_content_type():
return ContentType.objects.get_for_model(ProcurementRequest)