#8279 closed (fixed)
Models with multiple ManyToManyField to 'self' use the last table for every m2m
Reported by: | Owned by: | Malcolm Tredinnick | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Keywords: | ||
Cc: | 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 )
class Entry(models.Model): more_fields related = models.ManyToManyField('self', verbose_name=_('related entries')) translations = models.ManyToManyField('self', verbose_name=_('translations'))
From the database (postgres):
blango=# select * from blango_entry_related; id | from_entry_id | to_entry_id ----+---------------+------------- 1 | 27 | 29 2 | 29 | 27 3 | 27 | 30 4 | 30 | 27 5 | 27 | 38 6 | 38 | 27 (6 rows) blango=# select * from blango_entry_translations; id | from_entry_id | to_entry_id ----+---------------+------------- 3 | 4 | 3 4 | 3 | 4 7 | 8 | 7 8 | 7 | 8 11 | 13 | 12 12 | 12 | 13 (6 rows) blango=#
And from ./manage.py shell:
In [2]: from blango.models import *;Entry.objects.get(pk=27).related.all() Out[2]: [] In [3]: from blango.models import *;Entry.objects.get(pk=27).translations.all() Out[3]: []
The database query log indicates that Django is using the wrong table:
2008-08-12 22:38:10 CEST LOG: statement: SELECT "blango_entry"."id", "blango_entry"."title", "blango_entry"."slug", "blango_entry"."author_id", "blango_entry"."language_id", "blango_entry"."body", "blango_entry"."body_html", "blango_entry"."pub_date", "blango_entry"."draft", "blango_entry"."allow_comments" FROM "blango_entry" INNER JOIN "blango_entry_translations" ON ("blango_entry"."id" = "blango_entry_translations"."to_entry_id") WHERE "blango_entry_translations"."from_entry_id" = 27
If I change the order of the m2m in the model to:
class Entry(models.Model): more_fields translations = models.ManyToManyField('self', verbose_name=_('translations')) related = models.ManyToManyField('self', verbose_name=_('related entries'))
Then I get all the related entries as related and as translations.
If it serves as help, I've added a print statement in django/db/models/fields/related.py at line 776:
... 775 else: 776 print '%s_%s' % (opts.db_table, self.name) 777 return '%s_%s' % (opts.db_table, self.name) ...
Which prints:
In [1]: from blango.models import *;Entry.objects.get(pk=27).related.all() blango_entry_related blango_entry_translations Out[1]: [] In [2]: from blango.models import *;Entry.objects.get(pk=27).related.all() blango_entry_related Out[2]: []
Change History (10)
comment:1 by , 16 years ago
milestone: | → 1.0 |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:2 by , 16 years ago
comment:3 by , 16 years ago
I forgot to add that t1.related2.add() and t1.related2.remove() work as expected. They write to the testapp_testmodel_related2 table.
comment:4 by , 16 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:5 by , 16 years ago
After some digging, it looks like a workaround for this is to set related_name.
Options.init_name_map() iterates through the m2m related fields, but f.field.related_query_name() is always 'entry'. This overwrites the other 'entry' key in the cache object, which is why it always joins on the final table.
comment:6 by , 16 years ago
Owner: | changed from | to
---|---|
Status: | assigned → new |
I was unsuccessful trying to backtrack through the code to figure out how to pass in a more descriptive search key. I'll let someone else more familiar with the code take it from here.
comment:8 by , 16 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:9 by , 16 years ago
Resolution: | → fixed |
---|---|
Status: | assigned → closed |
I've added some debug print statements, printing the join_table for the RelatedManager and the sql generated by the query. Here are the results:
In [6]: t1.related2.all()
"testapp_testmodel_related2"
('SELECT "testapp_testmodel"."id", "testapp_testmodel"."value" FROM "testapp_testmodel" INNER JOIN "testapp_testmodel_related3" ON ("testapp_testmodel"."id" = "testapp_testmodel_related3"."to_testmodel_id") WHERE "testapp_testmodel_related3"."from_testmodel_id" = %s ', (1,))
So join_table is correctly passed to the manager, but somewhere it overrides (or ignores it) an ends up using testapp_testmodel_related3 as the table for join (while it should use testapp_testmodel_related2).