Domanda

I need something like

user_messages = UserMessage.objects.filter(Q(from_user=user) | Q(to_user=user)).order_by('dialog_id').distinct('dialog_id').order_by('created')

Of course, it doesn't work. I found that I should use annotate(), but it seems to be quiet difficult for me, I'm new to Django. Can you help me?

È stato utile?

Soluzione

This is the code I have to implement a similar feature. Maybe it will be of use to you.

In the view:

queryset = Conversation.objects.filter(Q(user_1=user) | Q(user_2=user)).filter(
                Q(messages__sender=user, messages__archived_by_sender=False) |
                Q(messages__recipient=user, messages__archived_by_recipient=False)).annotate(
                last_activity=Max("messages__created")).order_by("-last_activity")

The models look like: (methods etc. omitted)

class Conversation(models.Model): # there is only one of these for each pair of users who message one another
    user_1 = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="conversations_as_user_1")
    user_2 = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="conversations_as_user_2")
    read_by_user_1 = models.BooleanField(default=False)
    read_by_user_2 = models.BooleanField(default=False)

    class Meta:
        unique_together = (("user_1", "user_2"),)

class Message(TimeTrackable):
    conversation = models.ForeignKey(Conversation, related_name="messages", blank=True, help_text="the conversation between these two users (ALWAYS selected automatically -- leave this blank!)")
    sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="messages_sent")
    recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="messages_received")
    text = models.TextField() # flat text, NOT bleached or marksafe
    archived_by_sender = models.BooleanField(default=False)
    archived_by_recipient = models.BooleanField(default=False)

This is for an application where you can never have multiple separate conversation objects between the same users, but you can use the archive feature to archive all of the messages (from your perspective) which is as good as deleting the conversation from the user's perspective. Also, "read" status is stored on the conversation, not on the message.

In order to guarantee no two users have multiple "Conversation" objects with one another, user_1 on a conversation is always the lower user ID, and user_2 is always the higher. (In truth, I didn't think of this clever idea in time to actually implement it, so instead I have complex and unnecessary overrided save() logic. But if I were doing it again, I would do this part too, and maybe even call the fields user_lower and user_higher or something like that to make it clear.)

Let's break the view code down:

Fetch all conversation objects where the current user is either user_1 or user_2. Via a filter, require that any conversation objects returned have at least some of the messages visible to the current user have not been archived. The conversations don't have timestamps, but the messages do, so annotate the list of conversations by the most recent activity. Then order by that annotated timestamp.

This avoids any distinct() work because you are fetching on the conversation list with a join instead of on the message list with a join.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top