Opened 3 years ago

Last modified 15 months ago

#17002 new Bug

ManyToManyField through a model which extends some other model

Reported by: mitar Owned by: nobody
Component: Database layer (models, ORM) Version: master
Severity: Normal Keywords:
Cc: mmitar@… Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no


It seems that currently it is impossible to create a ManyToManyField through a model which extends some other model. Django assumes that all fields are accessible in the given model and does not traverse the model hierarchy. This means that it is impossible to use inheritance to prevent data (and code) duplication and have a normalized database.

Attachments (0)

Change History (3)

comment:1 Changed 3 years ago by sebastian

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Triage Stage changed from Unreviewed to Design decision needed
  • Type changed from New feature to Bug
  • Version changed from 1.3 to SVN

Django expects the foreign key attributes to be present in the intermediary model given as the through= argument (and not some non-abstract parent) when constructing the related queries. On the other hand, model validation and table creation works even when the foreign key attributes are in the base class, or split over base and derived class as in e.g.

class A(models.Model):
class B(models.Model):
    a_s = models.ManyToManyField(A, through='Derived')

class Base(models.Model):
    a = models.ForeignKey(A)
class Derived(Base):
    b = models.ForeignKey(B)

The example validates just fine and even generates the appropriate database structure, but related queries fail during runtime, e.g. B.objects.get(pk=1).a_s.all() throws "DatabaseError: no such column: app_derived.a_id".

Can you give an example where foreign keys in the base class of the intermediary model (as opposed to arbitrary attributes, or methods) are necessary or would be useful?

In any case, I think the behavior here should be consistent. If the query cannot be created (because attributes are expected to be present in the derived table), then the validation of the model should not succeed. Or otherwise, the query should take attributes imported from base classes into account and insert the necessary joins.

comment:2 Changed 3 years ago by mitar

The concrete example where we discovered this is when we wanted to make a schema other users could extend. For exmaple, we have defined something like;

class Node(models.Model):
    children = models.ManyToManyField(Child, through='Link')

class Child(models.Model):

class Link(models.Model):
    source = models.ForeignKey(Node)
    destination = models.ForeignKey(Child)

And then we wanted to provide users a way to extend our schema with their own applications/modules. So that they can instead derive their own schema, like:

class BetterNode(Node):
    better_children = models.ManyToManyField(Child, through='BetterLink')

class BetterLink(Link):
    weight = models.IntegerField()

And then old applications could still use the old fields, while users' extended applications could know how to handle this additional fields.

comment:3 Changed 15 months ago by Alex

  • Triage Stage changed from Design decision needed to Accepted

Yeah this is a bug (at the very minimum a docs one, although I think it's definitely a "true" bug), I'm not sure how much of a pain it'll be to fix off hand.

Add Comment

Modify Ticket

Change Properties
<Author field>
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'

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

Note: See TracTickets for help on using tickets.