3

I'm using Django to write a social networking application, and need to implement a feature similar to the Facebook "Mutual Friends" concept. I have a simple model like this:

class Friend(models.Model):
    user = models.ForeignKey(User)
    guid = models.BigIntegerField()
    name = models.TextField()

    class Meta:
        unique_together = ['user', 'facebook_id']

It represents a relationship between a user of my site, and one of his friends. (The friends are not necessarily also users) The number of mutual friends between two users can be calculated as the intersection of their friend lists, in other words,

users = User.objects.all()
friends = Friend.objects.filter(user=request.user)
friend_ids = [f.guid for f in friends]
for user in users:
    user.mutual = Friend.objects.filter(user=user, guid__in=friend_ids).count()

(A more efficient way of doing the above will score bonus points).

My main question is, having calculated the number of mutual friends between the users, how can I now order the users queryset according to number of mutual friends of the current user? I cannot, in this case, save the count as a annotation or extra field, since it relies on the specific user being examined and only a certain subset of his total friends. Can I use the annotate or extra methods in some ingenious way? Or is a raw sql query the only way to go? If so, how?

To summarize:

Calculating the number of mutual friends for each user is not the problem. Given that information for each user, how do you then proceed to order the QuerySet according to that number?

Herman Schaaf
  • 46,821
  • 21
  • 100
  • 139

1 Answers1

3

Not sure if this is exactly what you are looking for but...

# get current users friends (assuming guid is unique for a friend?)
user_friend_guids = Friend.objects.values_list('guid', flat=True).filter(user=user)

# get Friend objects where user not current user, is in user_friend_list, group and count by user
mutual_friends = Friend.objects.values('user__username') \
                               .filter(guid__in=user_friend_guids).exclude(user=user) \
                               .annotate(number_mutual_friends=Count('user')) \
                               .order_by('-number_mutual_friends')

this would return a list of usernames with the amount of friends they share with user, ordered by the number they share.

then in the template:

{% for mutual_friend in mutual_friends %}
    {{mutual_friend.user__username}} - {{mutual_friends.number_mutual_friends}}
{% endfor %}
JamesO
  • 25,178
  • 4
  • 40
  • 42
  • I'm afraid not quite what I'm looking for: I'm looking to order the list of `User`s objects according to their number of mutual friends, not the `Friend`s objects. A small distinction, I know, but it's the ordering of the users that is giving me trouble. To clarify, `User` objects are the normal Django backend `User` objects, while `Friend` is defined as above, and do not represent actual users, only people in the contact list of my users. – Herman Schaaf Jul 20 '11 at 09:53
  • ok, for clarification the above would not return Friend objects. It would return a list in the format of [{"user__username":james,"number_mutual_friends":10},{"user__username":herman,"number_mutual_friends":6}], so you would get list of usernames (or the field you choose) but yes as you mentioned not user objects. If it has to be user objects, i think (someone else may answer) it would need to be done with raw SQL or defining mutual friends as a method and order via python e.g.http://stackoverflow.com/questions/981375/using-a-django-custom-model-method-property-in-order-by – JamesO Jul 20 '11 at 10:16
  • Ok, thanks. Ordering with python will unfortunately not be an option, as there may be 10000+ users. But yes, come to think of it now, your method looks like it might be all that's necessary. I'll play around a bit more, and get back (hopefully with a solution). Thanks! – Herman Schaaf Jul 20 '11 at 10:40
  • Yep, I achieved my goal by using something very similar to your suggestion, by first selecting all users who have mutual friends (and counting the friends) and then appending the queryset to a list of all other users (excluding the ones already selected). This gives a list sorted first by mutual friends, then other parameters. This is all I wanted, so... success :) – Herman Schaaf Jul 20 '11 at 12:34