193 lines
7.2 KiB
Python
193 lines
7.2 KiB
Python
from drf_spectacular.utils import extend_schema_field
|
||
from rest_framework import serializers
|
||
|
||
from tasks.models import (
|
||
TaskColumn,
|
||
TaskColumnRow,
|
||
TaskLabel,
|
||
)
|
||
|
||
from .common import validate_board_access
|
||
|
||
|
||
class TaskLabelSerializer(serializers.ModelSerializer):
|
||
class Meta:
|
||
model = TaskLabel
|
||
fields = ["id", "name", "color"]
|
||
read_only_fields = ["id"]
|
||
|
||
def validate_name(self, value):
|
||
name = value.strip()
|
||
duplicate = TaskLabel.objects.only("id", "name")
|
||
if self.instance:
|
||
duplicate = duplicate.exclude(pk=self.instance.pk)
|
||
normalized_name = name.casefold()
|
||
if any(label.name.casefold() == normalized_name for label in duplicate):
|
||
raise serializers.ValidationError("Метка с таким названием уже существует.")
|
||
return name
|
||
|
||
|
||
class TaskColumnSerializer(serializers.ModelSerializer):
|
||
tasks_count = serializers.IntegerField(read_only=True, default=0)
|
||
subcolumns_count = serializers.SerializerMethodField()
|
||
|
||
class Meta:
|
||
model = TaskColumn
|
||
validators = []
|
||
fields = [
|
||
"id",
|
||
"board",
|
||
"parent",
|
||
"name",
|
||
"position",
|
||
"color",
|
||
"is_done",
|
||
"is_archived",
|
||
"tasks_count",
|
||
"subcolumns_count",
|
||
"created_at",
|
||
"updated_at",
|
||
]
|
||
read_only_fields = [
|
||
"id",
|
||
"created_at",
|
||
"updated_at",
|
||
"tasks_count",
|
||
"subcolumns_count",
|
||
]
|
||
|
||
def validate_board(self, board):
|
||
return validate_board_access(self, board)
|
||
|
||
@extend_schema_field(serializers.IntegerField())
|
||
def get_subcolumns_count(self, obj):
|
||
annotated_count = getattr(obj, "subcolumns_count", None)
|
||
if annotated_count is not None:
|
||
return annotated_count
|
||
return obj.subcolumns.filter(is_archived=False).count()
|
||
|
||
def _validate_immutable_relations(self, attrs, board, parent) -> None:
|
||
if self.instance and board and self.instance.board_id != board.id:
|
||
raise serializers.ValidationError(
|
||
{"board": "Доску существующей колонки изменить нельзя."}
|
||
)
|
||
if self.instance and "parent" in attrs:
|
||
requested_parent_id = parent.id if parent else None
|
||
if self.instance.parent_id != requested_parent_id:
|
||
raise serializers.ValidationError(
|
||
{"parent": "Родителя существующей колонки изменить нельзя."}
|
||
)
|
||
|
||
@staticmethod
|
||
def _validate_parent(attrs, board, parent) -> None:
|
||
if not parent:
|
||
return
|
||
if board and parent.board_id != board.id:
|
||
raise serializers.ValidationError(
|
||
{"parent": "Подколонка должна принадлежать той же доске."}
|
||
)
|
||
if parent.parent_id:
|
||
raise serializers.ValidationError(
|
||
{"parent": "Допускается только один уровень подколонок."}
|
||
)
|
||
if parent.is_archived:
|
||
raise serializers.ValidationError(
|
||
{"parent": "Нельзя добавить подколонку в архивную колонку."}
|
||
)
|
||
attrs["is_done"] = parent.is_done
|
||
|
||
def _validate_archive(self, attrs) -> None:
|
||
archiving_parent = (
|
||
self.instance
|
||
and attrs.get("is_archived") is True
|
||
and self.instance.subcolumns.exists()
|
||
)
|
||
if archiving_parent:
|
||
raise serializers.ValidationError(
|
||
{"is_archived": "Сначала удалите подколонки."}
|
||
)
|
||
|
||
def _validate_unique_name(self, attrs, board, parent) -> None:
|
||
name = attrs.get("name", getattr(self.instance, "name", ""))
|
||
if not board or not name:
|
||
return
|
||
duplicate_columns = TaskColumn.objects.filter(name=name)
|
||
if parent:
|
||
duplicate_columns = duplicate_columns.filter(parent=parent)
|
||
else:
|
||
duplicate_columns = duplicate_columns.filter(
|
||
board=board,
|
||
parent__isnull=True,
|
||
)
|
||
if self.instance:
|
||
duplicate_columns = duplicate_columns.exclude(pk=self.instance.pk)
|
||
if duplicate_columns.exists():
|
||
raise serializers.ValidationError(
|
||
{"name": "Колонка с таким названием уже существует здесь."}
|
||
)
|
||
|
||
def validate(self, attrs):
|
||
board = attrs.get("board") or getattr(self.instance, "board", None)
|
||
parent = attrs.get("parent", getattr(self.instance, "parent", None))
|
||
self._validate_immutable_relations(attrs, board, parent)
|
||
self._validate_parent(attrs, board, parent)
|
||
self._validate_archive(attrs)
|
||
self._validate_unique_name(attrs, board, parent)
|
||
return attrs
|
||
|
||
def create(self, validated_data):
|
||
parent = validated_data.get("parent")
|
||
if parent:
|
||
validated_data.setdefault("color", parent.color)
|
||
validated_data.setdefault("is_done", parent.is_done)
|
||
return super().create(validated_data)
|
||
|
||
|
||
class TaskColumnRowSerializer(serializers.ModelSerializer):
|
||
tasks_count = serializers.IntegerField(read_only=True, default=0)
|
||
|
||
class Meta:
|
||
model = TaskColumnRow
|
||
fields = [
|
||
"id",
|
||
"column",
|
||
"name",
|
||
"position",
|
||
"color",
|
||
"tasks_count",
|
||
"created_at",
|
||
"updated_at",
|
||
]
|
||
read_only_fields = ["id", "tasks_count", "created_at", "updated_at"]
|
||
|
||
def validate_column(self, column):
|
||
validate_board_access(self, column.board)
|
||
if column.parent_id:
|
||
raise serializers.ValidationError(
|
||
"Дорожки можно создавать только в основной колонке."
|
||
)
|
||
if column.is_archived:
|
||
raise serializers.ValidationError(
|
||
"Нельзя создать дорожку в архивной колонке."
|
||
)
|
||
return column
|
||
|
||
def validate(self, attrs):
|
||
column = attrs.get("column") or getattr(self.instance, "column", None)
|
||
if self.instance and column and self.instance.column_id != column.id:
|
||
raise serializers.ValidationError(
|
||
{"column": "Основную колонку дорожки изменить нельзя."}
|
||
)
|
||
name = attrs.get("name", getattr(self.instance, "name", "")).strip()
|
||
if not name:
|
||
raise serializers.ValidationError({"name": "Укажите название дорожки."})
|
||
duplicate = TaskColumnRow.objects.filter(column=column, name__iexact=name)
|
||
if self.instance:
|
||
duplicate = duplicate.exclude(pk=self.instance.pk)
|
||
if duplicate.exists():
|
||
raise serializers.ValidationError(
|
||
{"name": "Дорожка с таким названием уже существует в колонке."}
|
||
)
|
||
attrs["name"] = name
|
||
return attrs
|