0

I am currently writing an application that matches users based on similar interests, the subjects they study, where they live, etc.

I have my user creation/login system written and working. However, I am struggling to get started with the matching algorithm. After logging in to the site, I'd like it to be able iterate through other users' interests (which is a many-to-many field to the interest model) and then return a list of similar users, users that have one or more interests the same as, or are on the same course as the logged in user.

What would be the best way to implement this into a Django app?

Any help and advice is greatly appreciated!

I have attached a snippet of my models code for reference. Many thanks in advance

models.py

class Interest(models.Model):
    title = models.TextField()

    def __unicode__(self):
        return self.title


class Location(models.Model):
    location = models.CharField(max_length=50)

    def __unicode__(self):
        return self.location


class Course(models.Model):
    title = models.CharField(max_length=40)
    faculty = models.CharField(max_length=40)

    def __unicode__(self):
        return self.title

class MyUser(AbstractBaseUser):
    email = models.EmailField(
                        verbose_name='email address',
                        max_length=255,
                        unique=True,
                    )
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    date_of_birth = models.DateField()
    course = models.ForeignKey(Course, null=True)
    location = models.ForeignKey(Location, null=True)
    interests = models.ManyToManyField(Interest, null=True)
    bio = models.TextField(blank=True)

    objects = MyUserManager()

class MyUserManager(BaseUserManager):
    def create_user(self, email, date_of_birth, password=None):
        """
        Creates and saves a User with the given email, date of
        birth and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=MyUserManager.normalize_email(email),
            date_of_birth=date_of_birth,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, date_of_birth, password):
        """
        Creates and saves a superuser with the given email, date of
        birth and password.
        """
        u = self.create_user(email=email,
                        password=password,
                        date_of_birth=date_of_birth
                    )
        u.is_admin = True
        u.save(using=self._db)
        return u
MrW
  • 173
  • 1
  • 1
  • 12
  • What does 'similar users' mean, exactly? What makes a user similar? – Daniel Roseman Jan 17 '15 at 21:41
  • A similar User would be one that has one or more interests the same as you, or one that is on the same course as you. I realise this is not very clear, I shall edit my original post to clear things up a bit. – MrW Jan 17 '15 at 21:44

1 Answers1

3

If you have a MyUser object mu, you can get all of that user's interests with mu.interests.all(). You can also use __in in a lookup to test inclusion in a list or queryset. So if you want to filter for all MyUser objects that have interests in common with our mu user, you could do:

related_users = MyUser.objects.filter(interests__in=mu.interests.all())

This will produce a queryset with each MyUser object represented once for every interest match they have. For instance, if user1 has two interests in common with mu while user2 only has one common interest, this filter will give us:

[<MyUser: user1>, <MyUser: user1>, <MyUser: user2>]

If you want users represented only once regardless of the number of matches, you could use .distinct(). Being able to determine the number of matches may be useful to you in determining "similarity," however you ultimately choose to define that.

(Anyone reading this should also be interested in collections and the cool stuff within. Read this SO post for more: How can I count the occurrences of a list item in Python?)


Edit: you mentioned that you're hitting two errors:

AttributeErrors; "'QuerySet' object has no attribute 'interest'"

Your MyUser object has the interest attribute. A queryset containing a bunch of MyUser objects will not have that attribute, but the members of the queryset will.

Take the related_users queryset we defined above, for example. Since it's a queryset, we can do things like .filter() it or .get() a specific object from it. We can also treat it like a list and access individual objects with index locations, like related_users[0] - although since it's a queryset, we also have the special .first() method available as well.

However, we can't do related_users.interests.all() - the queryset doesn't have the interests attribute, the individual objects inside the queryset have it. If we did this:

>> for user in related_users:
>>    print user.interests.all()

Then we'd see the console print all of the interests for each user in the queryset, one user at a time. If you want to access the interests attribute, make sure you're working with a single MyUser object and not a whole queryset.

The other error:

"'ReverseManyRelatedObjectsDescriptor' object has no attribute 'all'"

You are probably doing something like MyUser.interests.all() - remember, MyUser is your model, not an instance of your model. You need to call .interests.all() on a single instance of the MyUser class, not the entire class at once. In other words:

>> u = MyUser.objects.get(id=1) #get a single MyUser object
>> u.interests.all()

Almost the same issue as the other one - if you want to access the interests attribute, make sure you're working with a single MyUser object and not the class definition.


I wondered if you could help me in implementing this into a view? How would I make sure that the single MyUser object is the user that is currently logged in?

Within your view you could access request.user (or self.request.user for a class-based view). That object should be the one you want. If it isn't, I noticed you're using an AbstractBaseUser - make sure if you have a custom auth model defined that it's properly configured, or you'll run into trouble here.

And would there be a way to display which interests the users have in common?

For both your initial user object and the user objects you grab to build your related_users queryset, you'll have access to .interests.all() - you can compare the two lists in your view and determine what elements they have in common.

Community
  • 1
  • 1
souldeux
  • 3,615
  • 3
  • 23
  • 35
  • Thank you for such a prompt answer! I have tried to play around with these methods in the shell. However, I am getting various AttributeErrors; "'QuerySet' object has no attribute 'interest'" and, when trying the methods in a view: "'ReverseManyRelatedObjectsDescriptor' object has no attribute 'all'". I'm afraid I'm a tad confused as to how to solve these errors. – MrW Jan 17 '15 at 22:09
  • I edited in a little more detail; I am taking a guess as to how you're hitting those errors, let me know if any of this doesn't make sense. – souldeux Jan 17 '15 at 22:34
  • That's so great, thank you very much. Finally, I wondered if you could help me in implementing this into a view? How would I make sure that the single MyUser object is the user that is currently logged in? And would there be a way to display which interests the users have in common? – MrW Jan 17 '15 at 23:00
  • Sorry to pester again, but have come across another error message after implementing this into a view. It worked fine in the shell, however when I try to use the `MyUser.objects.get(...)` method, it returns the TypeError "'MyUser' object does not support indexing". Why would this work fine in the shell, but not in views.py? – MrW Jan 19 '15 at 11:52
  • This sounds like a separate issue; it would be more appropriate to open a separate question thread. If you'd rather message me privately please feel free to use the contact form on my website, listed in my profile. – souldeux Jan 19 '15 at 17:48