# backend/api/v1/feed/serializers.py from __future__ import annotations from django.contrib.auth import get_user_model from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from feed.constants import TYPE_DEPARTMENT, TYPE_EMPLOYEE from feed.models import Post, PostLike from ..utils import build_media_url Employee = get_user_model() def _linked_task_payloads(post: Post, user) -> list[dict]: if not user or not getattr(user, "is_authenticated", False): return [] from tasks.models import TaskLinkedObjectKind from tasks.presentation import linked_task_badges_for_object return linked_task_badges_for_object( obj=post, kind=TaskLinkedObjectKind.POST, user=user, ) class AuthorMiniSerializer(serializers.ModelSerializer): full_name = serializers.SerializerMethodField() avatar = serializers.SerializerMethodField() class Meta: model = Employee fields = ["id", "first_name", "last_name", "full_name", "avatar"] @extend_schema_field(serializers.CharField()) def get_full_name(self, obj): fn = (obj.first_name or "").strip() ln = (obj.last_name or "").strip() nm = f"{fn} {ln}".strip() return nm @extend_schema_field(OpenApiTypes.URI) def get_avatar(self, obj): """Возвращает полный URL для аватара""" request = self.context.get("request") return build_media_url(obj.avatar, request) class PostLikerSerializer(AuthorMiniSerializer): class Meta(AuthorMiniSerializer.Meta): fields = ["id", "first_name", "last_name", "full_name", "avatar"] class PostListSerializer(serializers.ModelSerializer): author_id = serializers.IntegerField(read_only=True) author = AuthorMiniSerializer(read_only=True) department_name = serializers.CharField( source="department.name", read_only=True, allow_null=True, ) # удoбные представления дат created_at_display = serializers.DateTimeField( source="created_at", format="%d.%m.%Y %H:%M", read_only=True ) pinned = serializers.SerializerMethodField() pinned_global = serializers.BooleanField(read_only=True, default=False) pinned_department = serializers.BooleanField(read_only=True, default=False) # ожидания фронта is_liked = serializers.BooleanField(read_only=True, default=False) comments_count = serializers.IntegerField( read_only=True, default=0, allow_null=True ) # для ссылок на отдел department_id = serializers.IntegerField(read_only=True) # Переопределяем поля для полных URL image = serializers.SerializerMethodField() attachment = serializers.SerializerMethodField() linked_tasks = serializers.SerializerMethodField() class Meta: model = Post fields = [ "id", "type", "department", "department_id", "department_name", "title", "body", "image", "attachment", "created_at", "created_at_display", "pinned", "pinned_global", "pinned_department", "likes_count", "comments_count", "author_id", "author", "is_liked", "linked_tasks", ] read_only_fields = ( "created_at", "created_at_display", "pinned", "pinned_global", "pinned_department", "likes_count", "comments_count", "author_id", "author", "department_id", "department_name", "is_liked", "linked_tasks", ) @extend_schema_field(OpenApiTypes.URI) def get_image(self, obj): """Возвращает полный URL для изображения""" request = self.context.get("request") return build_media_url(obj.image, request) @extend_schema_field(OpenApiTypes.URI) def get_attachment(self, obj): """Возвращает полный URL для вложения""" request = self.context.get("request") return build_media_url(obj.attachment, request) @extend_schema_field(serializers.BooleanField()) def get_pinned(self, obj): scope = self.context.get("pin_scope") or "global" if scope == "department": return bool(getattr(obj, "pinned_department", False)) return bool(getattr(obj, "pinned_global", False)) def get_linked_tasks(self, obj) -> list[dict]: request = self.context.get("request") user = getattr(request, "user", None) return _linked_task_payloads(obj, user) class PostSerializer(PostListSerializer): """Сериализатор для деталей поста""" body = serializers.CharField(required=False, allow_blank=True) image = serializers.ImageField(required=False, allow_null=True, write_only=True) attachment = serializers.FileField(required=False, allow_null=True, write_only=True) user_has_liked = serializers.SerializerMethodField() class Meta(PostListSerializer.Meta): fields = PostListSerializer.Meta.fields + ["user_has_liked"] @extend_schema_field(serializers.BooleanField()) def get_user_has_liked(self, obj): """Проверяет, лайкнул ли текущий пользователь этот пост""" request = self.context.get("request") if request and request.user and request.user.is_authenticated: return PostLike.objects.filter(post=obj, user=request.user).exists() return False def to_representation(self, instance): data = super().to_representation(instance) request = self.context.get("request") data["image"] = build_media_url(instance.image, request) data["attachment"] = build_media_url(instance.attachment, request) return data def validate(self, attrs): # значения с учётом partial t = attrs.get("type", getattr(self.instance, "type", None)) dept = attrs.get("department", getattr(self.instance, "department", None)) # личные посты запрещены if t == TYPE_EMPLOYEE: raise serializers.ValidationError( {"type": "Публикация на личной странице запрещена."} ) # связка type/department if t == TYPE_DEPARTMENT and not dept: raise serializers.ValidationError( {"department": "Нужно указать отдел для новостей отдела."} ) if t != TYPE_DEPARTMENT and dept is not None: raise serializers.ValidationError( {"department": ("Для этого типа публикаций отдел указывать нельзя.")} ) return attrs