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