Fetch relations from ManyToMany Field using Annotation

Question:

I have my database here. Where I have 2 users connected to one instance of ChatRoomParticipants with a ManyToManyField.

I’m trying to get list of related users from a ManyToMany Relation Field from ChatRoomParticipants where I don’t want to show the currently authenticated user in the list with other fields i.e room present in the model.

Considering user f4253fbd90d1471fb54180813b51d610 is currently logged in and is related to all ChatRooms via ChatRoomParticipants model.

Things I’ve tried but couldn’t get the desired output

chatrooms = list(ChatRoomParticipants.objects.filter(user=user).values_list('user__chatroom_users__user__username', 'room').distinct())

#####
chatrooms =  ChatRoomParticipants.objects.filter(user=user).annotate(user!=User(user))

####
chatrooms = Chatrooms.objects.filter(user=user)
rooms = chatrooms.values('user__chatroom_users__user__username').distinct().exclude(user__chatroom_users__user__username=user)

I want an output like

[
    {
        'user': '872952bb6c344e50b6fd7053dfa583de'
        'room': 1
    },
    {
        'user': '99ea24b12b8c400689702b4f25ea0f40'
        'room': 2
    },
    {
        'user': 'eecd66e748744b96bde07dd37d0b83b3'
        'room': 3
    },
]

models.py

class ChatRoom(models.Model):
    name = models.CharField(max_length=256)
    last_message = models.CharField(max_length=1024, null=True)
    last_sent_user = models.ForeignKey(
        User, on_delete=models.PROTECT, null=True)

    def __str__(self):
        return self.name


class Messages(models.Model):
    room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)
    user = models.ForeignKey(User, on_delete=models.PROTECT)
    content = models.CharField(max_length=1024)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content


class ChatRoomParticipants(models.Model):
    user = models.ManyToManyField(User, related_name='chatroom_users')
    room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)
Asked By: coder

||

Answers:

I think the database design is wrong. Especially, the user field in the ChatRoomParticipants model is defined as ManyToManyField but I think it should be just as ForeignKey because there needs to be the M2M relationship between User and ChatRoom, not between User and ChatRoomParticipants.

class ChatRoomParticipants(models.Model):
    user = models.ForeignKey(User, related_name='chatroom_users', on_delete=models.PROTECT)
    room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)

Then the filter function should work.

First you need to get the list of rooms that the current authenticated user is in:

room_ids = list(ChatRoomParticipants.objects.filter(user=user).values_list('room__id', flat=True))

And you get the roommates:

participants = ChatRoomParticipants.objects.exclude(user__id = user.id).filter(room__id__in = room_ids)
Answered By: Metalgear

if you still want to keep your DB design then check the bellow link for query reference

[https://docs.djangoproject.com/en/4.0/topics/db/queries/#lookups-that-span-relationships]

Answered By: Jephtah Jephtah

You can do the query from the User model.

from django.db.models import F

User.objects.exclude(
    username=user
).annotate(
    room=F(
        'chatroomparticipants__room'
    )
).filter(room__isnull=False)

Also the != in your annotation you should consider look at the Q class
example,

With that you can do this negation.

from django.db.models import Q

User.objects.filter(
    ~Q(username=user)
)