Home > Back-end >  Order by nested objects date
Order by nested objects date

Time:11-09

I am building a chat application and have the following models representing the chat room, chat message and the counter of unread messages

class ChatRoom(BaseModel):
    name = models.CharField(max_length=255, default='', blank=True)
    participants = models.ManyToManyField('accounts.User', related_name='chat_rooms')

class ChatMessage(BaseModel):
    text = models.TextField(default='', blank=True)
    owner = models.ForeignKey('accounts.User', on_delete=models.CASCADE)
    room = models.ForeignKey(ChatRoom, on_delete=models.CASCADE, related_name='messages')

class ChatRoomUnreadMessagesCounter(BaseModel):
    room = models.ForeignKey(ChatRoom, on_delete=models.CASCADE, related_name='unread_counters')
    owner = models.ForeignKey('accounts.User', on_delete=models.CASCADE)
    messages_count = models.IntegerField(default=0)

class BaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True

I would like to make an API view that would return a list of ChatRoom objects that are ordered in the following manner: ChatRoom objects that have unread messages ordered by messages count -> ChatRoom objects that have messages in them by the latest message created_at last in first out -> ChatRoom objects that are sorted alphabetically. How would I construct my queryset properly?

Here's an expected result output

| Room Name | Unread Messages | Last Message          |
| Rean      | 2               | 09.11.2021T12:00:00   |
| Ash       | 1               | 09.11.2021T12:00:24   |
| Altina    | 0               | 09.11.2021T12:15:00   |
| Juna      | 0               | 09.11.2021T12:14:00   |
| Kurt      | 0               | 09.11.2021T12:13:00   |
| Musse     | 0               | 09.11.2021T12:12:00   |
| Sharon    | 0               | No messages yet       |

CodePudding user response:

This is not really trivial. Hope someone gets a simpler answer. Mine includes SubQuery.

    from django.db.models import OuterRef, Subquery

    # get query which references an 'id' field of the parent qs
    latest_message_subq = Subquery(Message.objects.filter(
        room=OuterRef("id")).order_by("-created_at").values('created_at')[:1]
    )

    # annotate the unread count per room
    # assumes there's only a single counter per owner
    unread_count_subq = Subquery(ChatRoomUnreadMessagesCounter.objects.filter(
        room=OuterRef("id"), owner=user).values('messages_count')[:1]
    )

    # Room.objects.all() is the parent qs
    # So the OuterRef("id") is pointing to a room id
    rooms = Room.objects.annotate(
        latest_message_time=latest_message_subq,
        unread_count= unread_count_subq
    )
    # every object in the rooms qs should now have the attributes latest_message_time and unread_count; you can loop it to verify

    rooms = rooms.order_by('-unread_count', '-latest_message', 'name')

EDIT: Changed to get a Room qs at the end

CodePudding user response:

You must add the logic in the __lt__ dunder lt method in order to tell python how to sort the instances.

Here is the official documentation

This thread can be useful.

  • Related