ManyToManyField in both models

38 views
Skip to first unread message

Evgeniy Ivanov (powerfox)

unread,
Jan 15, 2009, 1:35:29 PM1/15/09
to Django users
Hi list,
I want to have ManyToMany fields in both models to make django
generate forms with multiselect for both models.
Something like this:

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

First of all without related_name specified this code doesn't work.
Secondly it will create 2 link/relation tables which can surprise new
django users. But according the documentation it's normal behavior. If
I specify db_table in both tables it will still generate 2 tables,
which will make syncdb failed:

#class taken from http://www.djangosnippets.org/snippets/962/
class ManyToManyField(models.ManyToManyField):
def __init__(self, *args, **kwargs):
source = kwargs.pop('source_db_column', None)
reverse = kwargs.pop('reverse_db_column', None)
if source is not None:
self._m2m_column_name_cache = source
if reverse is not None:
self._m2m_reverse_name_cache = reverse
super(ManyToManyField, self).__init__(*args, **kwargs)

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

reverse_db_column='GROUP_ID',

db_table=u'Users_To_Groups')
class Group(models.Model):
users = ManyToManyField(User, related_name='users',
source_db_column='GROUP_ID',

reverse_db_column='USER_ID',

db_table=u'Users_To_Groups')
It will generate 2 identical (only fields order differs) tables
Users_To_Groups. And I guess it will work if I create table manually
(since syncdb doesn't touch already created tables). With my patch
from ticket #10037 it will not have extra id field.

Another variant should work: http://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
I will get everything working, but with extra ID column in the
database.
Extra ID is created for single ManyToManyField too:
http://code.djangoproject.com/ticket/10037
I think it's wrong.

How can I add m2m to both models without using third model with
foreign keys (or with it, but without extra ID field)? If to be
sincere it doesn't make much sense, but everything in Django should be
perfect :)

Malcolm Tredinnick

unread,
Jan 15, 2009, 9:16:18 PM1/15/09
to django...@googlegroups.com
On Thu, 2009-01-15 at 10:35 -0800, Evgeniy Ivanov (powerfox) wrote:
> Hi list,
> I want to have ManyToMany fields in both models to make django
> generate forms with multiselect for both models.
> Something like this:
>
> class User(models.Model):
> groups = models.ManyToManyField('Group', related_name='groups')
> class Group(models.Model):
> users = models.ManyToManyField(User, related_name='users')
>
> First of all without related_name specified this code doesn't work.
> Secondly it will create 2 link/relation tables which can surprise new
> django users.

It's completely logical. You've specified two separate relations here,
not the same relation from both ends.

[...]


> How can I add m2m to both models without using third model with
> foreign keys (or with it, but without extra ID field)? If to be
> sincere it doesn't make much sense, but everything in Django should be
> perfect :)

You only specify the field on one model and it's available at both ends.
This is a design feature so that you *don't* have to write the same
thing twice. So the answer to your question is to specify the ManyToMany
field on one model.

So the solution to your problem is "don't do that". "Perfect" doesn't
mean you can use whatever syntax you like. In Django, each time you
write ManyToManyField, you are specify a new relation between two
models.

You need to go back and look at your original problem. You wanted forms
with multiselect fields for both forms. So the real question is "how do
you do that". I suspect the answer is that you have to manually
construct one of the forms (which you can write a helper function to do
if you need this in a lot of projects, or, if this only happens for one
model, just write a single form class). The other answer is to rethink
your data entry so that this kind of bi-directional multi-select isn't
necessary. It's not a really common UI pattern and you can probably
think of ways to avoid it.

Regards,
Malcolm

Evgeniy Ivanov (powerfox)

unread,
Jan 16, 2009, 4:33:57 AM1/16/09
to Django users


On Jan 16, 5:16 am, Malcolm Tredinnick <malc...@pointy-stick.com>
wrote:
> > How can I add m2m to both models without using third model with
> > foreign keys (or with it, but without extra ID field)? If to be
> > sincere it doesn't make much sense, but everything in Django should be
> > perfect :)
>
> You only specify the field on one model and it's available at both ends.
> This is a design feature so that you *don't* have to write the same
> thing twice. So the answer to your question is to specify the ManyToMany
> field on one model.

You're right.

> You need to go back and look at your original problem. You wanted forms
> with multiselect fields for both forms. So the real question is "how do
> you do that". I suspect the answer is that you have to manually
> construct one of the forms (which you can write a helper function to do
> if you need this in a lot of projects, or, if this only happens for one
> model, just write a single form class).

Thanks, will try. Current sollution (create table manually and specify
name/columns) is neither right or cute.

> The other answer is to rethink
> your data entry so that this kind of bi-directional multi-select isn't
> necessary. It's not a really common UI pattern and you can probably
> think of ways to avoid it.

I agree, but it's not my idea.

Malcolm Tredinnick

unread,
Jan 16, 2009, 4:47:41 AM1/16/09
to django...@googlegroups.com
On Fri, 2009-01-16 at 01:33 -0800, Evgeniy Ivanov (powerfox) wrote:
>
>
> On Jan 16, 5:16 am, Malcolm Tredinnick <malc...@pointy-stick.com>
> wrote:

[...]


> > You need to go back and look at your original problem. You wanted forms
> > with multiselect fields for both forms. So the real question is "how do
> > you do that". I suspect the answer is that you have to manually
> > construct one of the forms (which you can write a helper function to do
> > if you need this in a lot of projects, or, if this only happens for one
> > model, just write a single form class).
>
> Thanks, will try. Current sollution (create table manually and specify
> name/columns) is neither right or cute.

I think part of the conceptual problem here is that you're trying to
solve your problem by working on the model definition. But the problem
is a form issue. It's about presentation, not data storage. So approach
it from that end.

Regards,
Malcolm

Evgeniy Ivanov (powerfox)

unread,
Jan 16, 2009, 11:26:39 AM1/16/09
to Django users


On Jan 16, 12:47 pm, Malcolm Tredinnick <malc...@pointy-stick.com>
wrote:
Malcolm, thanks for your help.
Today I was reading docs for some another things (filtering, etc) and
found clear explanation of the thing we discuss:
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-intermediary-models

Evgeniy Ivanov (powerfox)

unread,
Jan 21, 2009, 10:05:08 AM1/21/09
to Django users
Hi,

I've returned to the problem and noticed, that recipe described in the
docs is a kind of wrong: intermediary model is just a kind of M2M (I
mean multiselection) since you have select widgets for inline model
and to make real m2m editing have to add more inlines.

Custom model can help, but this hack is ugly too:

==========================================================================================================
class Groups(ModelForm):
users = forms.ModelMultipleChoiceField(queryset=Users.objects.all
(),required=False)

widget=admin.widgets.FilteredSelectMultiple(_("Users"), False))
class Meta:
model = Groups
==========================================================================================================

Now you have to add custom saving! It's wrong IMHO.
I think it would be much better to specify both M2M fields with the
same table name.

On Jan 16, 7:26 pm, "Evgeniy Ivanov (powerfox)"
<lolkaanti...@gmail.com> wrote:
> On Jan 16, 12:47 pm, Malcolm Tredinnick <malc...@pointy-stick.com>
> wrote:
>
>
>
> > On Fri, 2009-01-16 at 01:33 -0800, Evgeniy Ivanov (powerfox) wrote:
>
> > > On Jan 16, 5:16 am, Malcolm Tredinnick <malc...@pointy-stick.com>
> > > wrote:
>
> > [...]
>
> > > > You need to go back and look at your original problem. You wantedforms
> > > > with multiselect fields forbothforms. So the real question is "how do
> > > > you do that". I suspect the answer is that you have to manually
> > > > construct one of theforms(which you can write a helper function to do
> > > > if you need this in a lot of projects, or, if this only happens for one
> > > > model, just write a single form class).
>
> > > Thanks, will try. Current sollution (create table manually and specify
> > > name/columns) is neither right or cute.
>
> > I think part of the conceptual problem here is that you're trying to
> > solve your problem by working on the model definition. But the problem
> > is a form issue. It's about presentation, not data storage. So approach
> > it from that end.
>
> Malcolm, thanks for your help.
> Today I was reading docs for some another things (filtering, etc) and
> found clear explanation of the thing we discuss:http://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-...

Malcolm Tredinnick

unread,
Jan 21, 2009, 5:20:37 PM1/21/09
to django...@googlegroups.com
On Wed, 2009-01-21 at 07:05 -0800, Evgeniy Ivanov (powerfox) wrote:
> Hi,
>
> I've returned to the problem and noticed, that recipe described in the
> docs is a kind of wrong: intermediary model is just a kind of M2M (I
> mean multiselection) since you have select widgets for inline model
> and to make real m2m editing have to add more inlines.
>
> Custom model can help, but this hack is ugly too:
>
> ==========================================================================================================
> class Groups(ModelForm):
> users = forms.ModelMultipleChoiceField(queryset=Users.objects.all
> (),required=False)
>
> widget=admin.widgets.FilteredSelectMultiple(_("Users"), False))
> class Meta:
> model = Groups
> ==========================================================================================================
>
> Now you have to add custom saving! It's wrong IMHO.

How is it wrong that when you want a custom form you have to actually
write some code? Django cannot read your mind. It cannot possibly
accommodate every single use-case of every person on the planet out of
the box. It's designed to be extensible for precisely that reason.

"I have to write some code" is not a bad thing. Django is a library for
programmers.

> I think it would be much better to specify both M2M fields with the
> same table name.

Well, have lots of fun implementing your own field type to do this, if
it's what you want.

Django's ManyToManyField is, and always will be, specified on exactly on
model. That describes the data and the programmatic API. Trying to hack
that to modify the presentation of a form is still attacking the wrong
end of the problem.

Malcolm

Evgeniy Ivanov (powerfox)

unread,
Jan 22, 2009, 8:41:09 AM1/22/09
to Django users
On Jan 22, 1:20 am, Malcolm Tredinnick <malc...@pointy-stick.com>
wrote:
>
> How is it wrong that when you want a custom form you have to actually
> write some code? Django cannot read your mind. It cannot possibly
> accommodate every single use-case of every person on the planet out of
> the box. It's designed to be extensible for precisely that reason.
>
> "I have to write some code" is not a bad thing. Django is a library for
> programmers.

I understand. But you know, every developer has own opinion. Maybe in
this case it's a bad taste of mine.
And I agree that django is a very cute, well designed and powerful
library. Since I came from C++ world I call django "Qt for web
development" :)

> > I think it would be much better to specifybothM2M fields with the
> > same table name.
>
> Well, have lots of fun implementing your own field type to do this, if
> it's what you want.
>
> Django's ManyToManyField is, and always will be, specified on exactly on
> model. That describes the data and the programmatic API. Trying to hack
> that to modify the presentation of a form is still attacking the wrong
> end of the problem.

Sorry, but I can disagree a bit. I've written what can be done to make
it work in a way I've described here:
http://groups.google.com/group/django-developers/t/6a91f3523a69c2b
And it is a kind of ManyToManyField.through, isn't it? If we return to
the beginning of discussion:

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

I don't feel it abnormal. Yes, it can be my fault and also I work with
DB and django only about one month, so please forgive my arguing.
This code works fine with django, but with the only exception: syncdb
tries to generate 2 tables. It would be nice to specify something like
"create_table=False" in the second M2M, and it will be very like
"through" option. It's my humble opinion.
Reply all
Reply to author
Forward
0 new messages