简体   繁体   中英

How can I correct my ORM statement to show all friends not associated with a user in Django?

In my Django application, I've got two models, one Users and one Friendships. There is a Many to Many relationship between the two, as Users can have many Friends, and Friends can have many other Friends that are Users.

How can I return all friends (first and last name) whom are NOT friends with the user with the first_name='Daniel'?

Models.py:

class Friendships(models.Model):
    user = models.ForeignKey('Users', models.DO_NOTHING, related_name="usersfriend")
    friend = models.ForeignKey('Users', models.DO_NOTHING, related_name ="friendsfriend")
    created_at = models.DateTimeField(blank=True, null=True)
    updated_at = models.DateTimeField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'friendships'


class Users(models.Model):
    first_name = models.CharField(max_length=45, blank=True, null=True)
    last_name = models.CharField(max_length=45, blank=True, null=True)
    created_at = models.DateTimeField(blank=True, null=True)
    updated_at = models.DateTimeField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'users'

So far, here's what I've tried in my controller (views.py) -- please note, I understand controllers should be skinny but still learning so apologies. What I tried in the snippet below (after many failed attempts at a cleaner method) was to try and first grab friends of daniels (populating them into a list and then removing any duplicate ids), and then filter them out by their id.

# show first and last name of all friends who daniel is not friends with:

def index(req):

    friends_of_daniel = Friendships.objects.filter(user__first_name='Daniel')
    daniels_friends = []
    for friend_of_daniel in friends_of_daniel:
        daniels_friends.append(friend_of_daniel.friend.id)

    daniels_friends = list(set(daniels_friends))

    not_daniels_friends = Friendships.objects.exclude(id__in=daniels_friends)

    context = {
        'not_daniels_friends':not_daniels_friends,
    }

return render(req, "friendapp/index.html",context)

However, when I try the following in my views (templates) file, I still see individuals whom are friends of Daniels. Any idea what I'm doing wrong?

<ul>
    {% for not_daniel_friend in not_daniels_friends %}
        <li>{{ not_daniel_friend.user.first_name }} {{ not_daniel_friend.user.last_name }}</li>
    {% endfor %}
</ul>

I guess something like this will do. Just then take the list users , and get the first and last name of those users.

daniels = Users.objects.filter(first_name="Daniel") # There may be more than one Daniel
users = Friendships.objects.exclude(friend__in=daniels)

Note here, while Friendships.friend is a foreignkey of type Users you can pass Users instances (ie daniels list) in friend__in to exclude those users.

Try this,In the place of friend_of_daniel.friend.id , You should exclude the results from User model.

Something like this :

    def index(req):

        friends_of_daniel = Friendships.objects.filter(user__first_name='Daniel')
        daniels_friends = []
        for friend_of_daniel in friends_of_daniel:
            daniels_friends.append(friend_of_daniel.friend.id)

        daniels_friends = list(set(daniels_friends))

        not_daniels_friends = Users.objects.exclude(id__in=daniels_friends)

        context = {
            'not_daniels_friends':not_daniels_friends,
        }

    return render(req, "friendapp/index.html",context)

Thanks.

Firstly as a general comment: a cleaner way of populating a list of ids is using the .value_list() method from django (part of the .values() method in previous versions of Django). It has a "flat" flag that creates the list you want.

So, instead of:

friends_of_daniel = Friendships.objects.filter(user__first_name='Daniel')
    daniels_friends = []
    for friend_of_daniel in friends_of_daniel:
        daniels_friends.append(friend_of_daniel.friend.id)

    daniels_friends = list(set(daniels_friends))

You could do, in one line:

daniels_friends = Friendships.objects \
       .filter(user__first_name='Daniel') \
       .distinct('friend') \
       .values_list('friend', flat=True)

distinct makes the same as your list() - set() cast (it makes sure that your list has no repeated elements) and values_list with flat=True can be customizable to any field in the related "user" table: .values_list('friend__id', flat=True) or .values_list('friend__first_name', flat=True) to get a list of first_names of Daniel's friends.

Coming back to your general question, you can do the whole query directly in one line using your related_names, as I am not really sure of what you want (an user instance, a Friendship instance or just a list of firsts and last names) I will give you many options:

  • If you want a Friendship instance (what you are trying in your sample code):

     friendships_not_friends_with_daniel = Friendships.objects\\ .exclude(friend__first_name="Daniel") 

    This is equivalent to what @Rafael proposes in his answer:

    daniels = Users.objects.filter(first_name="Daniel") # There may be more than one Daniel users = Friendships.objects.exclude(friend__in=daniels)

    Here I am embedding his first query in the exclude by referencing the field in the related table with double underscore (which is an very powerful standard in Django).

  • If you want an User instance:

     users_with_no_friendship_with_daniel = Users.objects\\ .exclude(usersfriend__friend__first_name="Daniel") 

    Here you are using the related name of your model to access from the users table to the friendships table, and then check if the friend of this user is called Daniel. This way of querying is a bit complex to understand but as soon as you get used to it becomes really powerful because it is very similar to the spoken language: you want all users, but excluding the ones that have a friendship, whose friend's first name is Daniel. Depending on how many friends an user hat or how many users are called Daniel, you might to add some distinct() methods or split the query in two.

    As an advice, maybe you could improve the related name in your model, because it is what you would use if you have an user instance and want to get the related friendships: user_instance.friendships instead of user_instance.usersfriend and user_instance.friendsfriendships instead of user_instance.friendsfriend.... Do not know, it is always difficult to me to choose good related names...

  • If you want a list of tuples of users first and last names:

     names_of_users_with_no_friendship_with_daniel = Users.objects\\ .exclude(usersfriend__friend__first_name="Daniel")\\ .values_list('first_name', 'last_name') 

I am sorry if something is not clear, please ask and I try to explain better. (I am quite new in stackoverflow)

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