Обрезать длину сериализатора модели со многими = True в Django - PullRequest
4 голосов
/ 22 марта 2019

Так что в моем проекте у меня есть модель чата со многими сообщениями.Я хочу иметь ChatSerializer с MessageSerializer внутри и многими = True.

Однако я хочу включить только последние 50 моделей сообщений.

class MessageSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = Message
        fields = '__all__'

class ChatSerializer(serializers.ModelSerializer):
    messages = MessageSerializer(many=True)
    class Meta:
        model = Chat
        fields = '__all__'

Могу ли я добавить что-либо к messages = MessageSerializer(many=True) чтобы он возвращал только последние 50 сообщений?

Спасибо!

Ответы [ 4 ]

2 голосов
/ 22 марта 2019

Вы можете использовать SerializerMethodField , чтобы сделать это.

<b>MESSAGE_COUNT = 50
class ChatSerializer(serializers.ModelSerializer):
    messages = serializers.SerializerMethodField()
    class Meta:
        model = Chat
        fields = '__all__'

    def get_messages(self, obj):
        return MessageSerializer(obj.messages[:MESSAGE_COUNT ], many=True).data
</b>
1 голос
/ 22 марта 2019

Вы можете использовать SerializerMethodField в ChatSerializer следующим образом:

messages = serializers.SerializerMethodField()

def get_messages(self, chat):
    qs = Message.objects.filter(chat=chat).order_by('-date')[:50]
    return MessageSerializer(instance=qs, many=True).data

Это запускает отдельный запрос для каждого Chat экземпляра, но выбирает только необходимое количество строк. Вы должны настроить имена полей (chat, date) в зависимости от обстоятельств.


Альтернативный синтаксис @spiritsree нацелен на результаты в одном и том же SQL, используя неявную, а не явную фильтрацию:

qs = chat.messages.order_by('-date')[:50]

Единственное, чего следует избегать, это использовать prefetch_related('messages') в queryset из ViewSet, который возвращает список Chat, так как эта предварительная выборка не будет использоваться вообще и будет отбирать все сообщения из базы данных только для отказаться от использования.


Альтернатива подзапроса , отклоненная в другом ответе как медленная, на самом деле довольно интересна. Это сэкономит вам столько же обращений к базе данных, сколько и чатов. Однако взамен база данных должна выполнить вдвое больше внутренних запросов.

Из-за того, что лишние запросы достаточно легки (выбирая небольшое количество сообщений по идентификатору и упорядочивая их), сохраненные обходные пути могут легко восполнить их. В моих быстрых тестах этот метод был более чем в 10 раз быстрее, чем при использовании SerializerMethodField. Это может зависеть в некоторой степени от данных; Проверьте сами:

from rest_framework import viewsets
from django.db.models import Prefetch, Subquery, OuterRef

class ChatViewSet(viewsets.ModelViewSet):
    prefetch = Prefetch(
        'messages',
        queryset=Message.objects
            .filter(id__in=Subquery(Message.objects
                .filter(chat=OuterRef('chat_id'))
                .order_by('-date')
                .values_list('id', flat=True)[:4]))
            .order_by('-date')
    )

    queryset = Chat.objects.prefetch_related(prefetch)
0 голосов
/ 25 марта 2019

Все были правы насчет messages = serializers.SerializerMethodField().Одна вещь, которую нужно упомянуть, это упорядочить список, чтобы он был последним сообщением массива.

Для моей реализации мне нужно, чтобы последние 50 сообщений были отсортированы по возрастанию (получено позже), так что это то, что я сделал.

MESSAGE_COUNT = 50
class ChatSerializer(serializers.ModelSerializer):
    messages = serializers.SerializerMethodField()
    class Meta:
        model = Deal
        fields = '__all__'

    def get_messages(self, chat):
        qs = Message.objects.filter(deal=deal).order_by('timestamp')
        if len(qs) > MESSAGE_COUNT:
            qs = qs[len(qs)-MESSAGE_COUNT:]
        return MessageSerializer(instance=qs, many=True).data
0 голосов
/ 22 марта 2019

Ответ @Tobey не связан с вложенным ограничением и его следует игнорировать.

Ответ @spritsree заставит ограничение выполняться в Python, а не на уровне базы данных, где вы хотите, чтобы оно былобыть сделано.

Детали, которые вы ищете, не реализованы на уровне сериализатора.

Когда вы создаете экземпляр вашего сериализатора, вы должны передать ему данные, которые вы хотите сериализовать.В вашем случае вы должны выполнить это ограничение обратной связи при сборе данных из базы данных.

Я могу только предполагать, что вы являетесь отношением 1-N в сообщении чата.Следовательно, вы бы WISH могли бы сделать что-то подобное при написании QuerySet:

QuerySet = Chat.objects.all().prefetch_related(
    Prefetch(
        "messages", queryset=Message.objects.all().order_by("-created_at")[:50]
    )
)

Однако Django не поддерживает нарезку в наборах запросов, связанных с предварительной выборкой .Описан обходной путь, но вам нужно выполнить запрос IN, и это самый медленный способ решения этой проблемы.

Вместо этого вы должны разделить сбор сообщений в отдельный запрос:

# assuming that you are only interested in a single chat
chat = Chat.objects.latest("created_at")
messages = Message.objects.filter(chat=chat).order_by("created_at")[:50]

# instantiate serializer
serializer = ChatSerializer(data={"chat": chat, "messages": messages, ...})
serializer.data
...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...