简体   繁体   中英

How to filter django model by its objects in many-to-many field (exact match)?

I have this model in my code:

class Conversation(models.Model):
    participants = models.ManyToManyField(User, related_name="message_participants")

and I need to filter this "Conversation" model objects by the "participants" many-to-many field. meaning: I have for example 3 User objects, so I want to retrieve the only "Conversation" objects that has this 3 Users in it's "participants" field.

I tried doing this:

def get_exist_conv_or_none(sender,recipients):
    conv = Conversation.objects.filter(participants=sender)
    for rec in recipients:
        conv = conv.filter(participants=rec)

where sender is a User object and "recipients" is a list of User objects. it won't raise error but it gives me the wrong Object of Conversation. Thanks.

edit: A more recent try lead me to this:

def get_exist_conv_or_none(sender,recipients):
    participants=recipients
    participants.append(sender)
    conv = Conversation.objects.filter(participants__in=participants)
    return conv

which basically have the same problem. It yields Objects which has one or more of the "participants" on the list. but what Im looking for is exact match of the many-to-many object. Meaning, an Object with the exact "Users" on it's many-to-many relation.

edit 2: My last attempt. still, won't work.

def get_exist_conv_or_none(sender,recipients):
    recipients.append(sender)
    recipients = list(set(recipients))
    conv = Conversation.objects.annotate(count=Count('participants')).filter(participants=recipients[0])
    for participant in recipients[1:]:
        conv.filter(participants=participant)
    conv.filter(count=len(recipients))
    return conv

Ok so I found the answer: In order to make an exact match I have to chain-filter the model and then make sure it has the exact number of arguments it needs to have, so that the many-to-many field will have in it all the objects needed and no more.

I will check for the objects number using annotation: ( https://docs.djangoproject.com/en/dev/topics/db/aggregation/ )

ended up with this code:

def get_exist_conv_or_none(recipients):
    conv = Conversation.objects.annotate(count=Count('participants')).filter(participants=recipients[0])
    for participant in recipients[1:]:
        conv = conv.filter(participants=participant)
    conv = conv.filter(count=len(recipients))
    return conv

For fast search using database index, I use this code:

class YandexWordstatQueue(models.Model):
    regions = models.ManyToManyField(YandexRegion)
    regions_cached = models.CharField(max_length=10000, editable=False, db_index=True)
    phrase = models.ForeignKey(SearchPhrase, db_index=True)
    tstamp = models.DateTimeField(auto_now_add=True)

class YandexWordstatRecord(models.Model):
    regions = models.ManyToManyField(YandexRegion)
    regions_cached = models.CharField(max_length=10000, editable=False, db_index=True)
    phrase = models.ForeignKey(SearchPhrase, db_index=True)
    Shows = models.IntegerField()
    date = models.DateField(auto_now_add=True)

@receiver(m2m_changed, sender=YandexWordstatRecord.regions.through)
@receiver(m2m_changed, sender=YandexWordstatQueue.regions.through)
def yandexwordstat_regions_changed(sender, **kwargs):
    if kwargs.get('action') in ['post_add', 'post_remove']:
        instance = kwargs.get('instance')
        l = list(instance.regions.values_list('RegionID', flat=True))
        l.sort()
        instance.regions_cached = json.dumps(l)
        instance.save()

This adds overhead when saving, but now I can perform fast filter with this snippet:

region_ids = [1, 2, 3] # or list(some_queryset.values_list(...))
region_ids.sort()
regions_cahed = json.dumps(region_ids)
YandexWordstatQueue.objects.filter(regions_cached=regions_cached)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM