简体   繁体   中英

Wrong output from Many-To-Many relationship queryset in Django models

I have the following Django models:

class User(models.Model):
    username = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=100)

class Url(models.Model):
    user = models.ManyToManyField(User)
    url = models.URLField()

class Keywords(models.Model):
    url = models.ManyToManyField(Url)
    keyword = models.CharField(max_length=100)

Now, I want all the common keywords for any two users from the database:

username1 = 'user1'
username2 = 'user2'

Attempt 1:

Keyword.objects.filter(url__user__username=username1).filter(url__user__username=username2).values('keyword', 'url__url').distinct()

// Returns empty list []   [ Wrong ]

Attempt 2:

k1 = [ k.keyword for k in Keyword.objects.filter(url__user__username=username1) ]
k2 = [ k.keyword for k in Keyword.objects.filter(url__user__username=username2) ]

common_k = list(set(k1).intersection(set(k2)))
print common_k

// Return list of common keys (As Expected) [ Correct ]

What am I doing wrong in Attempt 1?

Please note: at first .filter(url__user__username=username1).filter(url__user__username=username2) seems wrong, but it has a many-to-many relationship in which it should work.

Test input for Attempt 1

newuser1 = User.objects.get(username='newuser1')
newuser2 = User.objects.get(username='newuser2')

url = Url(url='http://common.com/')
url.save()
url.user.add(newuser1)
url.user.add(newuser2)

key = Keyword(keyword='common')
key.save()
key.url.add(url)

Now, I tried Attempt 1 and Attempt 2 on this and got correct result as expected. I get common as keyword for newuser1 and newuser2 .

Now, Attempt 2 is definitely correct, then what am I doing wrong in Attempt 1?

Attempt1 is filtering the urls down to those with a user with name username1, and then filtering the results of that query to those with user with name username2. But the first list can only contain those with username1, so the result of the second filter will always be empty.

However, you find an intersection between these two queries in Attempt2 , rather than applying them in sequence. This is quite different, and will give the correct answer.

You could add this method to your user class, so that you can do user1.commonKeywordsWithUser(user2) , ie

class User(models.Model):
    username = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=100)

    def commonKeywordsWithUser(user):
        k1 = [ k.keyword for k in Keyword.objects.filter(url__user__username=self.name) ]
        k2 = [ k.keyword for k in Keyword.objects.filter(url__user__username=user.name) ]

        return list(set(k1).intersection(set(k2)))
set_1 = set(Keyword.objects.filter(url__user__username=username1).values_list('keyword'))
set_2 = set(Keyword.objects.filter(url__user__username=username2).values_list('keyword'))
common_keywords = list(set_1 & set_2)

Solution with single Query:

from django.db.models import Q
Keyword.objects.filter(url__user__username=username1).exclude(~Q(url__user__username=username2)).values('keyword', 'url__url').distinct()

Solution with single Query(different urls from 2 users yielding same keyword not removed):

from django.db.models import Q
Keyword.objects.filter(url__user__username=username1).exclude(~Q(url__user__username=username2)).values('keyword', 'url__url').distinct('keyword', 'url__url')

您可以尝试以下内容(对于尝试1):

Keyword.objects.filter(url__user__username=username1, keyword__in=Keyword.objects.filter(url__user__username=username2).values_list('keyword', flat=True)).values_list('keyword', flat=True).distinct()

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