276 lines
9.5 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.

"""Write representation and validation for documents."""
import os
from typing import Any
from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework import serializers
from documents.application import (
DocumentAudienceUpdate,
create_document,
update_document,
)
from documents.models import Document
from ..fields import FilerFileField as FilerFileSerializerField
from .recipient_ids import RecipientIDsField
User = get_user_model()
class DocumentWriteSerializer(serializers.ModelSerializer):
"""Сериализатор для записи/обновления документа.
Поддерживает:
- загрузку файла (multipart) через django-filer
- sent_to_all
- department_ids (отделы-получатели)
- recipient_ids (repeat/JSON/CSV) при sent_to_all=false
- tag_ids (теги)
"""
title = serializers.CharField(
required=False,
allow_blank=True,
max_length=255,
help_text="Название документа. Если пусто, будет взято из имени файла.",
)
extracted_text = serializers.CharField(required=False, allow_blank=True)
recipient_ids = RecipientIDsField(
write_only=True,
required=False,
help_text=("Список ID сотрудников (repeat/JSON/CSV) при sent_to_all=false."),
)
department_ids = RecipientIDsField(
write_only=True,
required=False,
help_text="Список ID отделов-получателей.",
)
acknowledgement_recipient_ids = RecipientIDsField(
write_only=True,
required=False,
help_text="Список ID сотрудников, обязанных ознакомиться.",
)
acknowledgement_department_ids = RecipientIDsField(
write_only=True,
required=False,
help_text="Список ID отделов, обязанных ознакомиться.",
)
tag_ids = RecipientIDsField(
write_only=True,
required=False,
help_text="Список ID тегов для привязки к документу.",
)
file = FilerFileSerializerField(
required=False,
allow_null=True,
help_text="Файл для загрузки через django-filer",
)
class Meta:
model = Document
fields = (
"id",
"title",
"description",
"file",
"folder",
"extracted_text",
"sent_to_all",
"is_regulation",
"acknowledgement_required",
"acknowledgement_for_all",
"department_ids",
"recipient_ids",
"acknowledgement_department_ids",
"acknowledgement_recipient_ids",
"tag_ids",
)
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
"""Согласованность полей.
Args:
attrs (dict): Данные после field-level валидаций.
Returns:
dict: Те же данные.
Raises:
serializers.ValidationError: Если sent_to_all=false
и нет recipient_ids или department_ids.
"""
file_obj = attrs.get("file")
if not str(attrs.get("title") or "").strip():
if file_obj:
file_name = getattr(file_obj, "original_filename", "") or getattr(
file_obj, "name", ""
)
default_title = os.path.splitext(os.path.basename(file_name))[0].strip()
attrs["title"] = default_title or file_name or "Документ"
elif self.instance is None:
attrs["title"] = "Документ"
elif self.instance is not None:
attrs.pop("title", None)
instance = self.instance
sent_to_all = attrs.get(
"sent_to_all",
instance.sent_to_all if instance is not None else True,
)
recipient_ids = set(
attrs.get(
"recipient_ids",
instance.recipients.values_list("pk", flat=True)
if instance is not None
else [],
)
)
department_ids = set(
attrs.get(
"department_ids",
instance.departments.values_list("pk", flat=True)
if instance is not None
else [],
)
)
if sent_to_all is False and not recipient_ids and not department_ids:
raise serializers.ValidationError(
{
"recipient_ids": (
"Укажите получателей, отделы или установите sent_to_all=true."
)
}
)
acknowledgement_required = attrs.get(
"acknowledgement_required",
instance.acknowledgement_required if instance is not None else True,
)
acknowledgement_for_all = attrs.get(
"acknowledgement_for_all",
instance.acknowledgement_for_all if instance is not None else True,
)
acknowledgement_recipient_ids = set(
attrs.get(
"acknowledgement_recipient_ids",
instance.acknowledgement_recipients.values_list("pk", flat=True)
if instance is not None
else [],
)
)
acknowledgement_department_ids = set(
attrs.get(
"acknowledgement_department_ids",
instance.acknowledgement_departments.values_list("pk", flat=True)
if instance is not None
else [],
)
)
if (
acknowledgement_required
and not acknowledgement_for_all
and not acknowledgement_recipient_ids
and not acknowledgement_department_ids
):
raise serializers.ValidationError(
{
"acknowledgement_recipient_ids": (
"Выберите сотрудников, отделы или включите "
"ознакомление для всех с доступом."
)
}
)
if acknowledgement_required and not acknowledgement_for_all and not sent_to_all:
invalid_departments = acknowledgement_department_ids - department_ids
if invalid_departments:
raise serializers.ValidationError(
{
"acknowledgement_department_ids": (
"Отдел для ознакомления должен иметь доступ к документу."
)
}
)
allowed_recipients = (
User.objects.filter(is_active=True)
.filter(
Q(pk__in=recipient_ids)
| Q(
departments_links__department_id__in=department_ids,
departments_links__is_active=True,
)
| Q(
role_assignments__role__department_id__in=department_ids,
role_assignments__is_active=True,
)
)
.values_list("pk", flat=True)
)
invalid_recipients = acknowledgement_recipient_ids - set(allowed_recipients)
if invalid_recipients:
raise serializers.ValidationError(
{
"acknowledgement_recipient_ids": (
"Сотрудник для ознакомления должен иметь доступ "
"к документу."
)
}
)
return attrs
@staticmethod
def _pop_audience_update(
validated_data: dict[str, Any],
*,
default,
) -> DocumentAudienceUpdate:
return DocumentAudienceUpdate(
recipient_ids=validated_data.pop("recipient_ids", default),
department_ids=validated_data.pop("department_ids", default),
acknowledgement_recipient_ids=validated_data.pop(
"acknowledgement_recipient_ids",
default,
),
acknowledgement_department_ids=validated_data.pop(
"acknowledgement_department_ids",
default,
),
tag_ids=validated_data.pop("tag_ids", default),
)
def create(self, validated_data: dict[str, Any]) -> Document:
"""Create a document through the atomic application command."""
audience = self._pop_audience_update(validated_data, default=())
request = self.context.get("request")
return create_document(
uploader=getattr(request, "user", None),
attributes=validated_data,
audience=audience,
)
def update(
self,
instance: Document,
validated_data: dict[str, Any],
) -> Document:
"""Update a document through the atomic application command."""
audience = self._pop_audience_update(validated_data, default=None)
return update_document(
document_id=instance.id,
attributes=validated_data,
audience=audience,
)