March 22, 2010 00:08 / 3 comments / django relationships

Doing a quick search on the Django Developers group for 'user model' yields a bunch of requests for extensible user models. There's also an epic ticket, #3011, opened 3 years ago on this topic. I don't really feel one way or the other about it -- this is just a roundabout way of excusing the hack you're about to see. I should first describe the problem, though...

The problem

I wrote a blog post not too long ago with some skimpy examples showing how to emulate Twitter and Facebook style relationships using self-referential many-to-many relationships. These examples worked fine, but they weren't using bona fide django.contrib.auth.models users. They used a 'Person' model, which could have had a one-to-one with a user. I wasn't satisfied with this approach - I want to be able to deal with the relationships intuitively, in the context of a user. I want the relationships to be an inline in the user admin. In short, I want to customize the User model.

I've since then been sort of playing with the idea of making describing relationships between users easy and somewhat transparent without forking django or altering the user table.

"Solution"

Many to many relationships again came to my rescue. By creating a completely separate model, Relationship, which has two foreign keys to users (as well as a status field and a created date, say), I have something that looks a lot like a through table.

http://www.charlesleifer.com/projects/gists/relationship-model/

I could have left it alone and queried my Relationship object, adding manager methods to get friends, but instead I opted to hack it onto the User model. To do this I instantiate a field in the module-level scope of the relationships' models.py and call its' contribute_to_class method, passing in the User model and the field name, which will be 'relationships':

http://www.charlesleifer.com/projects/gists/relationships-field/

To get all the rest of the convenience methods on the User model, I just used setattr:

http://www.charlesleifer.com/projects/gists/relationship-convenience-methods/

Please help!

I'd love to hear other folks' ideas on this. I've got a repo on github, django-relationships, and would love to see other solutions. For instance, it may be a cleaner solution to create a custom manager for Relationships and query through that (so User.relationships...).

Edit

I ended up changing the code to use a custom manager to handle relationship data. This is accessed like:

my_user.relationships.add(another_user)
my_user.relationships.friends()

I did this to avoid polluting the User namespace, plus it just seems more 'correct'. Here's the updated models.py:

http://www.charlesleifer.com/projects/gists/relationship-model-with-new-manager/

Comments (3)

  • Harro | March 2010, at 05:36

    Had to do kinda the same. But I didn't want to do hacks..

    So instead I wrote a very generic application which allows a user to follow whatever.

    I'll bookmark your application, if we ever need true friendships we'll use it :)


  • hackoder | March 2010, at 08:40

    This seems clean- I had a Relationship model similar to yours and then patched my User model like so:

    User.relationships = property(lambda u: Relationship.objects.filter(from_user=u)

    Since you mention that contribute_to_class is a hack, I'm assuming this is non-documented behavior?


  • Charles Leifer | March 2010, at 08:49

    I called it a hack since contribute_to_class is generally called for you by ModelBase


Commenting has been closed, but please feel free to contact me