333 lines
11 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 Count, Q
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 api.v1.employees.serializers.department import DepartmentBriefSerializer
from employees.models import Department
from tasks.models import (
TaskBoard,
TaskBoardAccessScope,
TaskBoardGroup,
TaskBoardPlacement,
TaskColumnRow,
TaskLabel,
TaskUserSettings,
)
from .structure import (
TaskColumnRowSerializer,
TaskColumnSerializer,
TaskLabelSerializer,
)
from .tasks import TaskSerializer
User = get_user_model()
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)
column_rows = serializers.SerializerMethodField()
tasks = TaskSerializer(many=True, read_only=True)
labels = serializers.SerializerMethodField()
tasks_count = serializers.IntegerField(read_only=True, default=0)
is_default_for_current_user = serializers.SerializerMethodField()
group_ids = serializers.SerializerMethodField()
group_id = serializers.SerializerMethodField()
is_pinned = serializers.BooleanField(
source="current_user_is_pinned",
read_only=True,
default=False,
)
notifications_enabled = serializers.BooleanField(
source="current_user_notifications_enabled",
read_only=True,
default=True,
)
view_state = serializers.SerializerMethodField()
class Meta:
model = TaskBoard
fields = [
"id",
"name",
"description",
"avatar",
"access_scope",
"created_by",
"members",
"member_details",
"departments",
"department_details",
"is_archived",
"columns",
"column_rows",
"tasks",
"labels",
"tasks_count",
"is_default_for_current_user",
"group_ids",
"group_id",
"is_pinned",
"notifications_enabled",
"view_state",
"created_at",
"updated_at",
]
@extend_schema_field(TaskLabelSerializer(many=True))
def get_labels(self, obj):
labels = getattr(self, "_global_labels_cache", None)
if labels is None:
labels = list(TaskLabel.objects.order_by("name", "id"))
self._global_labels_cache = labels
return TaskLabelSerializer(labels, many=True).data
@extend_schema_field(serializers.ListField(child=serializers.IntegerField()))
def get_group_ids(self, obj):
groups = getattr(obj, "current_user_groups", None)
if groups is None:
request = self.context.get("request")
user = getattr(request, "user", None)
if not user or not user.is_authenticated:
return []
groups = TaskBoardGroup.objects.filter(
Q(user=user) | Q(is_shared=True),
boards=obj,
).exclude(
hidden_by=user,
)
return sorted(group.id for group in groups)
@extend_schema_field(serializers.IntegerField(allow_null=True))
def get_group_id(self, obj):
group_ids = self.get_group_ids(obj)
return group_ids[0] if group_ids else None
def validate(self, attrs):
attrs = super().validate(attrs)
access_scope = attrs.get("access_scope")
if access_scope is None:
has_members = bool(attrs.get("members"))
has_departments = bool(attrs.get("departments"))
if has_members or has_departments:
access_scope = TaskBoardAccessScope.RESTRICTED
elif "members" in attrs and "departments" in attrs:
access_scope = TaskBoardAccessScope.ALL
else:
access_scope = getattr(
self.instance,
"access_scope",
TaskBoardAccessScope.ALL,
)
attrs["access_scope"] = access_scope
if access_scope == TaskBoardAccessScope.RESTRICTED and "members" in attrs:
existing_member_ids = (
set(self.instance.members.values_list("id", flat=True))
if self.instance is not None
else set()
)
if any(
not member.is_active and member.id not in existing_member_ids
for member in attrs["members"]
):
raise serializers.ValidationError(
{"members": "Добавлять на доску можно только активных сотрудников."}
)
if access_scope != TaskBoardAccessScope.RESTRICTED:
attrs["members"] = []
attrs["departments"] = []
return attrs
@extend_schema_field(serializers.DictField())
def get_view_state(self, obj):
request = self.context.get("request")
user = getattr(request, "user", None)
if not user or not getattr(user, "is_authenticated", False):
return {
"hidden_column_ids": [],
"collapsed_base_column_ids": [],
"collapsed_row_ids": [],
}
raw_state = (
TaskBoardPlacement.objects.filter(user=user, board=obj)
.values_list("view_state", flat=True)
.first()
or {}
)
valid_column_ids = set(
obj.columns.filter(parent__isnull=True, is_archived=False).values_list(
"id", flat=True
)
)
valid_row_ids = set(
TaskColumnRow.objects.filter(
column__board=obj,
column__is_archived=False,
).values_list("id", flat=True)
)
def valid_ids(key, allowed_ids):
values = raw_state.get(key, []) if isinstance(raw_state, dict) else []
if not isinstance(values, list):
return []
return sorted(
{
value
for value in values
if isinstance(value, int) and value in allowed_ids
}
)
return {
"hidden_column_ids": valid_ids("hidden_column_ids", valid_column_ids),
"collapsed_base_column_ids": valid_ids(
"collapsed_base_column_ids", valid_column_ids
),
"collapsed_row_ids": valid_ids("collapsed_row_ids", valid_row_ids),
}
@extend_schema_field(TaskColumnRowSerializer(many=True))
def get_column_rows(self, obj):
rows = (
TaskColumnRow.objects.filter(
column__board=obj,
)
.annotate(tasks_count=Count("tasks", distinct=True))
.order_by("column_id", "position", "id")
)
return TaskColumnRowSerializer(rows, many=True).data
@extend_schema_field(serializers.BooleanField())
def get_is_default_for_current_user(self, obj):
request = self.context.get("request")
user = getattr(request, "user", None)
if not user or not getattr(user, "is_authenticated", False):
return False
settings_obj = getattr(user, "task_settings", None)
return bool(settings_obj and settings_obj.default_board_id == obj.id)
class TaskBoardSummarySerializer(TaskBoardSerializer):
open_tasks_count = serializers.IntegerField(read_only=True, default=0)
completed_tasks_count = serializers.IntegerField(read_only=True, default=0)
class Meta(TaskBoardSerializer.Meta):
fields = [
"id",
"name",
"description",
"avatar",
"access_scope",
"created_by",
"member_details",
"department_details",
"tasks_count",
"open_tasks_count",
"completed_tasks_count",
"is_default_for_current_user",
"group_ids",
"group_id",
"is_pinned",
"notifications_enabled",
"created_at",
"updated_at",
]
class TaskBoardGroupSerializer(serializers.ModelSerializer):
boards_count = serializers.IntegerField(read_only=True, default=0)
is_default_for_current_user = serializers.SerializerMethodField()
is_hidden = serializers.SerializerMethodField()
is_owner = serializers.SerializerMethodField()
class Meta:
model = TaskBoardGroup
fields = [
"id",
"name",
"position",
"is_shared",
"boards_count",
"is_default_for_current_user",
"is_hidden",
"is_owner",
"created_at",
"updated_at",
]
read_only_fields = ["id", "created_at", "updated_at"]
def validate_name(self, value):
name = value.strip()
if not name:
raise serializers.ValidationError("Укажите название группы.")
request = self.context.get("request")
owner_id = (
self.instance.user_id
if self.instance
else getattr(getattr(request, "user", None), "id", None)
)
queryset = TaskBoardGroup.objects.filter(user_id=owner_id, name__iexact=name)
if self.instance:
queryset = queryset.exclude(id=self.instance.id)
if queryset.exists():
raise serializers.ValidationError(
"Группа с таким названием уже существует."
)
return name
@extend_schema_field(serializers.BooleanField())
def get_is_default_for_current_user(self, obj):
request = self.context.get("request")
user = getattr(request, "user", None)
if not user or not getattr(user, "is_authenticated", False):
return False
if not hasattr(self, "_default_group_id"):
self._default_group_id = (
TaskUserSettings.objects.filter(user=user)
.values_list("default_group_id", flat=True)
.first()
)
return self._default_group_id == obj.id
@extend_schema_field(serializers.BooleanField())
def get_is_hidden(self, obj):
hidden_by = getattr(obj, "current_user_hidden_by", None)
if hidden_by is not None:
return bool(hidden_by)
request = self.context.get("request")
user = getattr(request, "user", None)
return bool(
user
and getattr(user, "is_authenticated", False)
and obj.hidden_by.filter(id=user.id).exists()
)
@extend_schema_field(serializers.BooleanField())
def get_is_owner(self, obj):
request = self.context.get("request")
user = getattr(request, "user", None)
return bool(user and obj.user_id == getattr(user, "id", None))