Friday, April 29, 2011

filter for many to many field

so I have this model:

class Message(models.Model):
    creator = models.ForeignKey(User, unique=True)
    note = models.CharField(max_length=200, blank=True)
    recipients = models.ManyToManyField(User, related_name="shared_to")
    read = models.ManyToManyField(User, related_name="read", blank=True)

I want to filter on people who are in both recipients and read, currently I'm doing this.

messages = user.shared_to.all()

for message in messages:
    if user not in message.read:
        do something

I'm sure there is a way to filter this but I can't figure out how.

From stackoverflow
  • I think that I originally misread your question. Try the following query.

    for message in user.shared_to.exclude(read__id__in=[user.id]):
      do_something()
    
  • If you use the Django development version, or wait until version 1.1 is released, then your filters can reference other fields in the model. So, your query would look like this:

    >>> Message.objects.filter(reciepients=F('read'))
    

    (Note: I spelled reciepients the same as you had in your model, although the correct spelling would be "recipients")

    Edit:

    Ok, your question is confusing. When I gave the above answer I was reading your statement that you wanted all users who were in both "recipients" and "read"). But then reading your code snippet it looks like you want a list of users who are recipients, but not yet in the "read" list.

    This is probably what you want, but I'm not completely sure because of the vague requirements and I can't quickly test it:

    # Get list of recipients
    shared_to = Message.shared_to.all().values('id')
    
    # Now list of recipients who are not in the read field
    unread = Message.objects.exclude(recipients__in=shared_to)
    

    This will make a single query, but if you are using MySQL it could still be more efficient to do two queries (read the Performance considerations warning).

0 comments:

Post a Comment