Friendship_type on symmetrical M2M

14 views
Skip to first unread message

Henrik Lied

unread,
Apr 28, 2007, 3:42:55 PM4/28/07
to Django users
Hi there!

Scenario: User A goes to User B's profile. User A know User B, so User
A adds User B as his friend. He then specifies their friendship as
"Colleagues".

How can I make this work? I have extended the User-model to include a
friends-field (ManyToManyField("self")), but I have no idea how to
specify the friendship type for each of the friendships in a dynamic
way. Think Facebook. Only one of the users specify the friendship
type.

Any input here? :-)

Tim Chase

unread,
Apr 28, 2007, 5:41:45 PM4/28/07
to django...@googlegroups.com


It sounds like you want something like a secondary model to which
you can attach such attributes:

class Person(Model):
pass

class FriendshipType(Model):
# "colleague", "roommate", "teacher", etc
pass

class Friendship(Model):
person = Person()
friend = Person()
description = ForeignKey(FriendshipType)
known_since = DateTimeField()

to which you can do things like

my_friends = Friendship.objects.filter(
person=request.User).select_related()
for friendship in my_friends:
do_something('I have known my %s %s since %s' % (
friendship.description,
friendship.friend,
friendship.known_since,
))

in your view.

-tim


Henrik Lied

unread,
Apr 28, 2007, 7:27:18 PM4/28/07
to Django users
Thank you for your example, Tim.
I'm sorry, but the problem with it is that it isn't symmetrical.

A part of my Profile-model looks like this:
class Profile(models.Model):
user = models.ForeignKey(User)
friends = models.ManyToManyField("self")

This makes it very easy to create and maintain a relationship
between users - but I have no idea how to specify the friendship
type. Is it even possible?

Facebook has done this in a great way, and I'm guessing they're using
a similar approach to what I'm trying. But how the hell do they manage
to
specify the friendship type? That's what my brain is having problems
understanding.

What do you guys think?

Malcolm Tredinnick

unread,
Apr 28, 2007, 7:49:24 PM4/28/07
to django...@googlegroups.com
On Sat, 2007-04-28 at 23:27 +0000, Henrik Lied wrote:
> Thank you for your example, Tim.
> I'm sorry, but the problem with it is that it isn't symmetrical.
>
> A part of my Profile-model looks like this:
> class Profile(models.Model):
> user = models.ForeignKey(User)
> friends = models.ManyToManyField("self")
>
> This makes it very easy to create and maintain a relationship
> between users - but I have no idea how to specify the friendship
> type. Is it even possible?

Go back and have another look at Tim's suggestion. It is the right
approach. The problem isn't that the relationship is symmetrical, you're
right: the problem is that you cannot attach any extra attributes to the
relationship table (the table that connects the two ends) using
ManyToManyField.

Instead of trying to make the relationship a direct Many-to-Many, which
does not let you put any fields on the intermediate table, you need to
construct the intermediate table yourself via a couple of joins.

See, also
http://www.djangoproject.com/documentation/models/m2m_intermediary/ for
a similar example.

Regards,
Malcolm


Tim Chase

unread,
Apr 28, 2007, 8:02:35 PM4/28/07
to django...@googlegroups.com
> I'm sorry, but the problem with it is that it isn't symmetrical.
>
> A part of my Profile-model looks like this:
> class Profile(models.Model):
> user = models.ForeignKey(User)
> friends = models.ManyToManyField("self")
>
> This makes it very easy to create and maintain a relationship
> between users - but I have no idea how to specify the friendship
> type. Is it even possible?

Yes, it's possible...The models I provided do precisely that. A
M2M relationship uses/creates/maintains a table that Django keeps
rather well hidden from you unless you go looking for it or
poking in dark SQL corners. In your case, it's likely a table
called "app_profile_profile" or "app_profile_friends" or
something of the like. It consists of merely two columns: two
foreign keys just as my Friendship model did. What you describe
is wanting to add a column to this table. This is done by
promoting it to a full model.

If you need the syntactic sugar, you can use the related_name
parameter:

class Friendship(Model):
person = Person(related_name="friends")
friend = Person(related_name="thinks_i_am_a_friend")


description = ForeignKey(FriendshipType)
known_since = DateTimeField()

This allows you to iterate through

p = Person.objects.get(id=1)
friends = p.friends

rather than iterating through

friends = p.friendship_set

which you can then use as

for f in friends:
print f.friend, ' is my ', f.description

You can also find out who thinks a person is a friend:

friend_of = p.thinks_i_am_a_friend
for f in friend_of:
print "I am %s's %s whether or not they are my friend" % (
f.person, f.description)

This also meets your criterion that, X can consider Y a friend,
but Y doesn't have to consider X a friend. Which does reflect
the real world. Imagine if "description" was "Mom". If X is Y's
mom, but Y certainly wouldn't be X's mom. Unless you live in
some freaky world of inbreeding where you married your own father. :)

-tim

Henrik Lied

unread,
Apr 28, 2007, 8:02:54 PM4/28/07
to Django users
Thanks Malcolm!
One question:
In my example, it would be very easy to add and display friends.
If Joe added Murray as a friend, I would simply get Joe's friends
by doing something like:
v = Profile.objects.get(user=request.user)
v.friends.all()

And the approach would be the exact same for Murray.

In Tim's example, the model "Friendship" has two references to
the User-object.

So, if Joe added Murray as a friend, I'd get that out by doing
something
like:
v = Friendship.objects.filter(person=request.user)
for a in v:
#something

But - if I'm not too tired - this query wouldn't include Joe in the
output in
Murray's case.

Catch my drift?

It doesn't seem to be dynamic enough, if you understand what I mean.

Henrik Lied

unread,
Apr 28, 2007, 8:29:30 PM4/28/07
to Django users
Thanks for the quick reply!

On 29 Apr, 02:02, Tim Chase <django.us...@tim.thechases.com> wrote:
> This also meets your criterion that, X can consider Y a friend,
> but Y doesn't have to consider X a friend.

I'm sorry, maybe I haven't been clear enough on what I want to
achieve.
This little problem is part of a pretty large project. My employer has
spent enormous
resources on user testing on this point, and the fact is that people
want this process to
be quick and easy. If Joe adds Murray as a friend, and uses the pre-
filled description
"We worked together" when adding him, Murray simply wants to be able
to press
"Confirm", and the relationship is set up on both their profiles. This
is where
the symmetrical M2M is really handy - it doesn't rely on two separate
fields in the
table. The friendship on both sides is easily extracted through


something like
v = Profile.objects.get(user=request.user)
v.friends.all()

Do you get what I mean? I'm really sorry if I'm not clear enough!


> Imagine if "description" was "Mom". If X is Y's
> mom, but Y certainly wouldn't be X's mom. Unless you live in
> some freaky world of inbreeding where you married your own father. :)

I can assure you that this won't be a problem. The "description"-field
will
be very dynamic, as mentioned above ("We worked together" etc.)


Again, thank you for your input! This is an interesting
discussion. :-)

Tim Chase

unread,
Apr 28, 2007, 9:39:37 PM4/28/07
to django...@googlegroups.com
> be quick and easy. If Joe adds Murray as a friend, and
> uses the pre- filled description "We worked together" when
> adding him, Murray simply wants to be able to press
> "Confirm", and the relationship is set up on both their
> profiles. This is where the symmetrical M2M is really
> handy - it doesn't rely on two separate fields in the
> table. The friendship on both sides is easily extracted
> through something like
>
> v = Profile.objects.get(user=request.user)
> v.friends.all()

Have you experimented the solution I provided?

If you want something like this, you can easily tweak the
models. You can do something like

class Association(Model):
person = ForeignKey(Person, related_name='friend_of')
associate = ForeignKey(Person, related_name='friends')
relationship = ForeignKey(Relationship)
reciprocal = BooleanField(null=True,blank=True,default=None)

Thus, you can find out the list of people who are friends of
X where person X hasn't declared whether the relationship is
reciprocal:

p = Person.objects.get(id=42)
for a in Association.objects.filter(
associate=p).filter(
reciprocal__isnull=True)
print a.person, ' thinks I am a ', a.relationship
print "but I haven't said whether the opposite is the case"

You can then either set "reciprocal" to True or False based
on whether the user accepts the invitation, or you can
create another Association object with the backwards
information pre-populated. Either method would serve as
your "confirm" aspect of things...the value starts out set
to Null. The user is prompted about it until they choose
one way or the other.

-tim


Reply all
Reply to author
Forward
0 new messages