Code

Opened 8 years ago

Last modified 8 months ago

#897 new New feature

Bi-Directional ManyToMany in Admin

Reported by: anonymous Owned by: nobody
Component: contrib.admin Version:
Severity: Normal Keywords:
Cc: kmike84@…, carsten.fuchs@… Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by adrian)

Allow amnytomany relationships to be defined both ways
E.G

class ItemType(meta.Model):
    name = meta.CharField(maxlength=100)
    descritpion = meta.CharField(maxlength=250)

class PropertyType(meta.Model):
    name = meta.CharField(maxlength=100)
    itemtypes = meta.ManyToManyField(ItemType)

Excellent. When I make a new property type in the admin screens I get a
mutiselect window for item types.

What I want to be able to do however is have this work back the other
way too so that whe I create a new item i can specify what property
types apply.

Thanks

Attachments (0)

Change History (32)

comment:1 Changed 8 years ago by adrian

  • Description modified (diff)
  • priority changed from normal to low

comment:2 Changed 7 years ago by Gary Wilson <gary.wilson@…>

  • Summary changed from Bi-Directional ManyToMany to Bi-Directional ManyToMany in Admin

comment:3 Changed 7 years ago by Gary Wilson <gary.wilson@…>

#2648 marked as duplicate of this.

comment:4 Changed 7 years ago by SmileyChris

  • Triage Stage changed from Unreviewed to Accepted

It's a valid enhancement, and supported by a couple of requests.

comment:5 follow-up: Changed 7 years ago by gsf <gsf@…>

In the meantime, I was able to get what I think is the sought-after functionality by (continuing the above example):

class ItemType(meta.Model):
    name = meta.CharField(maxlength=100)
    description = meta.CharField(maxlength=250)
    properties = meta.ManyToManyField('PropertyType',
            db_table='app_propertytype_itemtypes')

class PropertyType(meta.Model):
    name = meta.CharField(maxlength=100)
    itemtypes = meta.ManyToManyField(ItemType)

Substitute "app" in "db_table" for the name of the app, of course.

comment:6 in reply to: ↑ 5 Changed 7 years ago by gsf <gsf@…>

This solution does break syncdb, however, because the same table wants to be created twice.

comment:7 Changed 6 years ago by mrts

  • milestone set to post-1.0

Non-essential for 1.0.

comment:8 Changed 6 years ago by gsf@…

The need for this has come up for me again, so I've been thinking about it. I think the best solution would be to allow for something similar to edit_inline on a ManyToManyField. I'll probably wait to work on it until after new-forms-admin gets rolled into trunk.

comment:9 Changed 6 years ago by gsf@…

Also, regarding the issue of recursion, I think the solution would be to either

  1. modify the "pop-up" add screen so that it doesn't contain the recursive m2m reference or
  2. remove the add button on both sides.

comment:10 Changed 6 years ago by NicoeEchaniz

I believe that what gsf tried to do is what most of us tried to do when we needed this functionality, only to find out that it breaks syncdb.

Would it be a bad idea to modify syncdb to accomodate for this case?

comment:11 Changed 6 years ago by russellm

Syncdb isn't the right place to do this - it isn't a model issue. This should be handled entirely at the forms level.

comment:12 Changed 5 years ago by anonymous

Some kind fellow posted a workaround on django snippets. http://www.djangosnippets.org/snippets/1295/

comment:13 Changed 5 years ago by anonymous

  • milestone post-1.0 deleted

Milestone post-1.0 deleted

comment:14 Changed 5 years ago by mrts

  • Keywords gsoc09-admin-refactor added

comment:15 Changed 5 years ago by jacob

  • Keywords gsoc09-admin-refactor removed

comment:16 Changed 4 years ago by buchuki

Doesn't look like there's been much activity on this bug for a while, but I'd like to point out that the workaround posted by anonymous (Django snippet #1295) no longer works with Django 1.2. I have been unable to find any other workarounds.

comment:17 follow-up: Changed 4 years ago by ericholscher

Just ran into this bug as well. I'll probably take a stab at fixing it in the next couple of days, as it is rather annoying. Though I'm curious if it might break expectations of people in existing setups.

comment:18 Changed 4 years ago by ericholscher

I talked to Alex Gaynor, and he mentioned ManytoMany inlines, which will solve this problem in a lot of cases.

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models

comment:19 Changed 4 years ago by Alex

Err, that was Carl Meyer who suggested it.

comment:20 Changed 4 years ago by russellm

There's at least one simple approach for backwards compatibility: reverse m2ms are only included if they are explicitly listed in the fields list. Alternatively, we add an 'include_reverse_m2m' flag.

For 2.0, we can consider dropping the flag and making reverse m2m the default. It's a little inconvenient, but that's the price of backwards compatibility.

comment:21 in reply to: ↑ 17 ; follow-up: Changed 3 years ago by anonymous

Replying to ericholscher:

Just ran into this bug as well. I'll probably take a stab at fixing it in the next couple of days, as it is rather annoying. Though I'm curious if it might break expectations of people in existing setups.

To fix it for 1.2, all needed is to change:

self.creates_table = False

to:

self.auto_created = False

comment:22 in reply to: ↑ 21 Changed 3 years ago by anonymous

Replying to anonymous:

Replying to ericholscher:

Just ran into this bug as well. I'll probably take a stab at fixing it in the next couple of days, as it is rather annoying. Though I'm curious if it might break expectations of people in existing setups.

To fix it for 1.2, all needed is to change:

self.creates_table = False

Forget this solution, it doesn't work. Sorry for the noise.

to:

self.auto_created = False

comment:23 Changed 3 years ago by etienned

I found a solution that works with 1.2. Here it is:

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

class Group(models.Model):
    users = models.ManyToManyField('User', through='UserGroups')

class UserGroups(models.Model):
    user = models.ForeignKey(User)
    group = models.ForeignKey(Group)

    class Meta:
        db_table = 'app_user_groups'
        auto_created = User

This way syncdb create the UserGroups table only one time (because users and groups have through as argument) but the admin think is auto_created so it show the ManyToManyField directly for both User and Group.

But that's definitely a lot of trouble to have the field in both model admin pages. I don't think the solution is to modified syncdb either. I think russellm's suggestions are good avenues.

comment:24 Changed 3 years ago by kmike

  • Cc kmike84@… added

comment:25 Changed 3 years ago by lrekucki

  • Severity changed from normal to Normal
  • Type changed from enhancement to New feature

comment:26 Changed 3 years ago by CarstenF

  • Cc carsten.fuchs@… added
  • Easy pickings unset

comment:27 Changed 3 years ago by croepha

  • UI/UX unset

etienned's solution did not work for me, I am doing this instead:

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

class Group(models.Model):
    users = models.ManyToManyField('User', db_table=User.groups.field.db_table)
Group.users.through._meta.managed = False

This will also work for non symmetrical self M2M relationships:

class User(models.Model):
  supervisors = models.ManyToManyField('self', related_name='underlings_set', db_table='testapp_user_supervisors')
  underlings = models.ManyToManyField('self', related_name='supervisors_set', db_table=supervisors.db_table)
  underlings._get_m2m_attr = supervisors._get_m2m_reverse_attr
  underlings._get_m2m_reverse_attr = supervisors._get_m2m_attr
User.underlings.through._meta.managed = False
Version 0, edited 3 years ago by croepha (next)

comment:28 Changed 3 years ago by anonymous

Check this out

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 guess this is the most native way

comment:29 Changed 19 months ago by CB

To update comment 28:

This works great, but breaks south. So here's a fix for that:

class ReverseManyToManyField(models.ManyToManyField):
    pass
try:
    import south
except ImportError:
    pass
else:
    from south.modelsinspector import add_ignored_fields
    add_ignored_fields([".*\.ReverseManyToManyField$",])

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

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

A possible Django enchancement would be adding this field (or rather, the results of it's contribute_to_class, I think) instead of the ReverseManyRelatedObjectsDescriptor

comment:30 Changed 12 months ago by rhunwicks

Using Django 1.5.1 I was getting:

app.test1: Reverse query name for m2m field 'tests2' clashes with m2m field 'Test2.tests1'. Add a related_name argument to the definition for 'tests2'

This is almost certainly because my field and model name is the same. Either way, I have solved it by adding related_name to Test1.tests2 and taking the opportunity to suppress it from Test2:

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

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

comment:31 Changed 10 months ago by kux

For people who still bump into this, it might be worth checking https://github.com/kux/django-admin-extend

It provides a mechanism for injecting bidirectional many-to-many fields in ModelAdmins that have already been defined by other apps.

comment:32 Changed 8 months ago by Koen Biermans <koen@…>

This seems to be more or less the same as #10964. I have a more generic solution for it on that ticket that allows you to use the reverse descriptor for M2M or nullable FK (like 'book_set') in the fields set for any modelform. It is also available in admin (also for filter_horizontal and filter_vertical).

Should one of these be considered as a duplicate?

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as new
The owner will be changed from nobody to anonymous. Next status will be 'assigned'
as The resolution will be set. Next status will be 'closed'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.