Opened 19 years ago
Last modified 2 weeks 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@…, cmawebsite@…, mmitar@…, Hugo Osvaldo Barrera, Emmanuel Katchy | 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 )
Allow manytomany 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 screen I get a
multiselect window for item types.
What I want to be able to do however is have this work back the other
way too so that when I create a new item I can specify what property
types apply.
Thanks
Change History (48)
comment:1 by , 19 years ago
Description: | modified (diff) |
---|---|
priority: | normal → low |
comment:2 by , 18 years ago
Summary: | Bi-Directional ManyToMany → Bi-Directional ManyToMany in Admin |
---|
comment:3 by , 18 years ago
comment:4 by , 18 years ago
Triage Stage: | Unreviewed → Accepted |
---|
It's a valid enhancement, and supported by a couple of requests.
follow-up: 6 comment:5 by , 17 years ago
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 by , 17 years ago
This solution does break syncdb, however, because the same table wants to be created twice.
comment:8 by , 16 years ago
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 by , 16 years ago
Also, regarding the issue of recursion, I think the solution would be to either
- modify the "pop-up" add screen so that it doesn't contain the recursive m2m reference or
- remove the add button on both sides.
comment:10 by , 16 years ago
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 by , 16 years ago
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 by , 16 years ago
Some kind fellow posted a workaround on django snippets. http://www.djangosnippets.org/snippets/1295/
comment:14 by , 16 years ago
Keywords: | gsoc09-admin-refactor added |
---|
comment:15 by , 16 years ago
Keywords: | gsoc09-admin-refactor removed |
---|
comment:16 by , 14 years ago
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.
follow-up: 21 comment:17 by , 14 years ago
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 by , 14 years ago
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:20 by , 14 years ago
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.
follow-up: 22 comment:21 by , 14 years ago
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 by , 14 years ago
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 by , 14 years ago
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 by , 14 years ago
Cc: | added |
---|
comment:25 by , 14 years ago
Severity: | normal → Normal |
---|---|
Type: | enhancement → New feature |
comment:26 by , 14 years ago
Cc: | added |
---|---|
Easy pickings: | unset |
comment:27 by , 13 years ago
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
comment:28 by , 13 years ago
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 by , 12 years ago
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 by , 12 years ago
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 by , 11 years ago
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 by , 11 years ago
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?
comment:33 by , 10 years ago
Cc: | added |
---|
I started trying to get the patches from #10964 applying cleanly (and correctly) to master, but I think it's worth waiting for the new _meta options API. https://github.com/django/django/pull/2894
comment:34 by , 10 years ago
The new _meta options api has landed. I'm very interested in seeing this make it across the finish line and would be happy to do the bulk of the work with a little bit of direction. (Not sure where the best place to post that kind of query).
comment:35 by , 10 years ago
Awesome. As some have mentioned, I think the next basic step make auto created reverse related fields (especially ManyToManyRel) into actual fields.
After that, we'd need to allow ManyToManyRel to be used in any model form.
Here's what I think would be a start. (Doesn't work at all)
https://github.com/django/django/pull/3927/files
comment:37 by , 9 years ago
comment:38 by , 9 years ago
Note the "should". I'd rather see the result of #24317 once committed before closing this one.
comment:41 by , 9 years ago
This snippet provides a workaround for this: https://snipt.net/chrisdpratt/symmetrical-manytomany-filter-horizontal-in-django-admin/
(and does it at the form level instead of with models)
comment:42 by , 9 years ago
Cc: | added |
---|
comment:43 by , 9 years ago
The form approach does not work correctly because in the history view of the model in admin there are no entries for changed reverse M2M fields.
comment:44 by , 6 years ago
Isn't this use case covered by this admin functionality?: https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#working-with-many-to-many-models
comment:45 by , 6 years ago
It can be done with inlines, but not with easy left/right select widget.
comment:46 by , 4 years ago
Cc: | added |
---|
comment:47 by , 16 months ago
Description: | modified (diff) |
---|
comment:48 by , 4 months ago
For clarity and to help whoever might take this up in the future:
This is still an issue in Django 5.2.dev20240621100134.
Given the following models
from django.db import models class Student(models.Model): name = models.CharField(max_length=50) age = models.PositiveIntegerField() def __str__(self) -> str: return self.name class Course(models.Model): name = models.CharField(max_length=20) students = models.ManyToManyField( to=Student, related_name="courses", related_query_name="course", ) def __str__(self) -> str: return self.name
and the admin.py
from django.contrib import admin from django.contrib.admin import ModelAdmin from core.models import Course, Student @admin.register(Course) class CourseAdmin(ModelAdmin): ... @admin.register(Student) class StudentAdmin(ModelAdmin): filter_horizontal = [ "course", ]
It raises an error
ERRORS: <class 'core.admin.StudentAdmin'>: (admin.E020) The value of 'filter_horizontal[0]' must be a many-to-many field.
This error is raised by django.contrib.admin.checks.BaseModelAdminChecks._check_filter_item.
However, getting the widget to render would require modifying ModelAdmin.get_form as well.
comment:49 by , 4 months ago
Cc: | added |
---|
comment:50 by , 2 weeks ago
I was not aware of this very old issue and opened a duplicate where I posted a workaround to make this work: https://code.djangoproject.com/ticket/35878#comment:1
#2648 marked as duplicate of this.