Отличная фильтрация по модели Django - PullRequest
0 голосов
/ 15 апреля 2011

Distinct может быть неправильным словом для того, что я хочу, но у меня есть класс 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, *1017*, from_user полях, слишком много потоков. Сервер БД растаял бы.

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

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

1 Ответ

0 голосов
/ 15 апреля 2011
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, чтобы найти каждый поток с правильным идентификатором. Они также оба фильтруются по одному и тому же запросу (по крайней мере, для объектов потока), чтобы гарантировать, что мы получаем только релевантные результаты для этого запроса.

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

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

потоки = отсортированы (потоки, ключ = лямбда-x: x.when, реверс = True)

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

...