Фильтрация в определенном стиле на модели Django

Отличительный может быть неправильным словом для того, что я хочу, но у меня есть класс Message, подобный следующему, для простой плоской системы обмена сообщениями между пользователями:

class Message(models.Model):
    thread = models.ForeignKey(Thread)
    from_user = models.ForeignKey(User, related_name='messagefromuser')
    to_user = models.ForeignKey(User, related_name='messagetouser')
    when = models.DateTimeField(auto_now_add=True)
    message = models.TextField()

Это позволяет двум пользователям обсуждать один объект Thread. Система предназначена для того, чтобы два пользователя могли вести отдельные разговоры в разных потоках.

Таким образом, я могу получить сообщения, в которых участвует данный пользователь, с помощью следующего запроса:

Message.objects.filter( Q(from_user=u) | Q(to_user=u) )

Это выводит каждое сообщение, отправленное или полученное пользователем. Я создаю страницу, на которой пользователи могут видеть все свои разговоры с другими пользователями, сгруппированные по темам. Вот идеальный вывод, который я могу себе представить:

[
    {
        'thread': thread_instance,
        'conversations': [
            {
                'other_user': user_instance
                'latest_reply': date_time_instance
            },
            ....
        ]
    },
    ...
]

Я думал об итерации этого сверху, но если нет способа фильтровать Thread в поля to_user, from_user Message, потоков слишком много. Сервер БД растает.

  • "Группировать" сообщения по Thread
  • "Группировать" группы другого пользователя, чтобы каждая группа находилась между двумя отдельными пользователями, за Thread
  • Возьмите самый последний to_user=u и аннотируйте что-нибудь с ним.

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


person Oli    schedule 15.04.2011    source источник
comment
почему нет свойств to_user и from_user потока? Возможно, я что-то упустил в прецеденте, но мне кажется, что проще получить представление, которое вы описали. Затем вы получаете сообщения потока только тогда, когда вам это нужно.   -  person OmerGertel    schedule 15.04.2011
comment
@OmerGertel (как ни странно) класс Thread на самом деле вообще не предназначен для обмена сообщениями. Это объект, который в какой-то момент сгенерирует один из двух пользователей в беседе. После создания другой пользователь может начать разговор с владельцем потока, а затем владелец может ответить. Если бы система была StackOverflow, Thread был бы вопросом, а Message был бы своего рода частной системой комментариев, обсуждающей вопрос.   -  person Oli    schedule 15.04.2011
comment
Но теперь, когда вы это сказали, я вижу возможность: я мог бы добавить еще один слой между Thread и Message, называемый MessageThread, чтобы создать служебный буфер между ними (точно так же, как это сделал бы настоящий поток обмена сообщениями). Система очень молода, поэтому я все еще могу позволить себе очистить данные, если это упростит задачу.   -  person Oli    schedule 15.04.2011
comment
MessageThread наверное хорошая идея   -  person OmerGertel    schedule 17.04.2011


Ответы (1)


threads = Thread.objects.filter(
  Q(message_set__from_user=u) | Q(message_set__to_user=u)
).order_by('id')

messages = Message.objects.filter(
  Q(thread__in=threads) & Q(Q(thread__from_user=u) | Q(thread_to_user=u))
).order_by('thread__id', '-when').select_related('from_user', 'to_user')

from itertools import groupby

t_index = 0
for thread_messages in groupby(messages, lambda x: x.thread_id):
  if threads[t_index].id is thread_messages[0].thread_id:
    threads[t_index].messages = thread_messages

  t_index += 1

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

Оба запроса упорядочены по одному и тому же полю, так что в нижней части кода мы можем выполнить итерацию по списку только один раз, вместо того, чтобы использовать вложенный цикл for для поиска каждого потока с правильным идентификатором. Они также фильтруются одним и тем же запросом (по крайней мере, в отношении объектов потока), чтобы гарантировать, что мы возвращаем только релевантные результаты для этого запроса.

Наконец, сообщения, которые мы получили в ответ, группируются и прикрепляются к каждой ветке; они будут отображаться в порядке убывания для каждого потока.

В самом конце вы также можете пересортировать свои потоки, чтобы сначала отображались самые последние, что легко сделать, предполагая наличие поля «когда» в модели потока:

threads = sorted(threads, key=lambda x: x.when, reverse=True)

Используя вышеупомянутый метод, вам придется каждый раз выполнять 2 запроса, независимо от того, сначала потоки, а затем сообщения. Но это никогда не выйдет за рамки этого (остерегайтесь объединений в select_related или рекурсивных запросов к связанным объектам).

person Dominic Santos    schedule 15.04.2011