23 January, 2009

ManyToMany relations in Django.

I didn't write anything for some time, since was very busy with my work and family problems.
But today I have some interesting thing about django, so I can't keep silence (I'm new to python/django, so it can be interesting for newbies only, but I hope it is not).

First of all I want to say sorry for indentation in quoting blocks... Will fix it later...

In django to define ManyToMany relation you should use ManyToManyField in your model. Consider folowing code:

class User(models.Model):
    groups = ManyToManyField('Group')
class Group(models.Model):

If you use admin or just manually create a form from the model, you will get a widget to edit M2M just for User. And it's normal since according to docs "By default, admin widgets for many-to-many relations will be displayed inline on whichever model contains the actual reference to the ManyToManyField". But in some rare cases you might need widgets be displayed on both models (some people think it's abnormal in common and some think you shouldn't tune your models for such tasks, but forms instead).

Since I was a newbie I had written:

class User(models.Model):
    groups = ManyToManyField('Group', related_name='groups')
class Group(models.Model):
    users = ManyToManyField(User, related_name='users')

And before I looked into database I thought I got expected result... Yes, django has created two different tables for two relations: User_Group and Group_User. And it's pretty logical for models.

Then you can try:

class User(models.Model):
    groups = ManyToManyField('Group', related_name='groups',
class Group(models.Model):
    users = ManyToManyField(User, related_name='users',

If you use it with existing db then it will be fine, but if you try syncdb it will try to create two identical tables. So we came to the final snippet:

class ManyToManyField_NoSyncdb(models.ManyToManyField):
    def __init__(self, *args, **kwargs):
        super(ManyToManyField_NoSyncdb, self).__init__(*args, **kwargs)
        self.creates_table = False

class User(models.Model):
    groups = ManyToManyField('Group',related_name='groups',db_table=u'USERS_TO_GROUPS')
class Group(models.Model):
    users = ManyToManyField_NoSyncdb(User,related_name='users',db_table=u'USERS_TO_GROUPS')

Here it is: now ManyToManyField widget is displayed on both forms and of cource on admin add/change pages for both models.

In case you use Django Admin you can try to inline intemediary model, but it will be just a kind of a many-to-many relation (no multiselect, but lot of inlines models). In our case it's not a good solution.

Since you work with m-2-m I want to point you to a good snippet for integrating legacy databases: http://www.djangosnippets.org/snippets/962/
In my code I have combined it with my snippet.


Martin said...

Powerfox, thanks for sharing! First hit on google for "django manytomany both models"... Very good example!

Pieter J. Kersten said...

Have you tried 'symmetrical = False' in ManyToManyField? It will create the same result: one table and related_name in the other class...
Saves some code.


powerfox said...

Sorry, didn't get notification about comments.
Pieter, docs say ManyToManyField.symmetrical should be used with ManyToManyFields on self only. So if the result is the same it could be a bug (and it can be fixed in next releases).

Philgo20 said...

So what's the good way to do this ? Does the use of symmetrical is ok ? I read the same in the doc but it would seem a little cleaner.

Lobo Mal said...

Dude, loved your snippet! Saved my as big time, here. Using symmetrical=False didn't do the trick here, tough.

Evgeniy Ivanov said...

I'm glad it has helped :)

Federico said...

Great snippet!
Most elegant solution I found for the problem.
If it's OK for you I'll post this in my (spanish) blog.
Obviously, I'll link back here as original post.

Evgeniy Ivanov said...

Federico, sure! I'm glad it helped you :-)

Valkyrie Savage said...

Still useful two years later. :) Thanks!

Gaz Robertson said...

This seems to work for me on Django 1.3 without causing a syncdb problem:

class User(models.Model):
groups = ManyToManyField('Group')
class Group(models.Model):
users = ManyToManyField(User,through=WebPage.categories.through)

Liu Bo said...

Very helpful, but in Django 1.2, it should like this:

class Test1(models.Model):
tests2 = models.ManyToManyField('Test2', blank=True)

class Test2(models.Model):
tests1 = models.ManyToManyField(Test1, through=Test1.tests2.through, blank=True)

I found that at this ticket.

Yellow Blade said...

Công ty vận chuyển hàng hóa nội địa chúng tôi xin giới thiệu các dịch vụ vận chuyển, dịch vụ ship hàng uy tín để phục vụ nhu cầu Tết của quý khách hàng. Cụ thể chúng tôi sẽ cung cấp dịch vụ chuyển quà tết. Chúng tôi sẽ giúp bạn vận chuyển hàng hóa đến tay người thân, bạn bè ở xa một cách nhanh chóng nhất. Đảm bảo giá cả hợp lý chất lượng dịch vụ tuyệt vời. Ngoài ra chúng tôi còn cung cấp nhiều dịch vụ khác như dịch vụ ship hàng cod, giao hàng cho shop, dịch vụ chuyển phát nhanh trong nước,... Nếu cần chuyển hàng hãy nhớ liên hệ với chúng tôi nhé.

Duong Qua said...

Cùng đồng môn với nhau vui vậy, bên bạn có chuyển hàng từ tphcm ra Hà Tĩnh, gia chuyen hang hoa di Nha Trang, chuyển hàng hóa từ tphcm đi Khánh Hoà không. Bên mình có lap dat thang may gia dinh gia re, thang may cho benh vien, lắp đặt thang máy tải hàng 1 tấn cần chuyen hang ra Vinh gia re để cung cấp cho dự án máy giữ xe, vậy bên bạn có gia cuoc van chuyen hang hoa rẻ không?