Opened 7 years ago

Last modified 9 hours ago

#7623 assigned New feature

Multi-table inheritance: Add the ability create child instance from existing parent

Reported by: brooks.travis@… Owned by: elektrrrus
Component: Database layer (models, ORM) Version: master
Severity: Normal Keywords: model-inheritance, multi-table-inheritance
Cc: brooks.travis@…, sirexas@…, anssi.kaariainen@…, mike@…, reza@…, cmawebsite@…, ar45 Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no


As it exists now, multi-table inheritance does not allow for the creation of a child model instance that inherits from an existing parent model instance. For example:

Parent Class-

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.TextField(max_length=150)

Child Classes-

class Restaurant(Place):
    place = models.OneToOneField(Place, parent_link=True)
    cuisine = models.CharField(max_length=75)
    rating = models.IntegerField()

class Bar(Place):
    parent = models.OneToOneField(Place, parent_link=True)
    happy_hour = models.BooleanField()
    beers_on_tap = models.ManyToManyField("Beers", null=True, blank=True)

Sample Use-case-

When the system is first deployed, a restaurant instance is created. Later, the restaurant adds a bar to increase revenue, and we now want to create a Bar model instance for the parent Place for the restaurant. I would propose the following interface for doing so:

parentPlace = Restaurant.objects.get(name__iexact="Bob's Place").parent
barInstance = Bar(parent=parentPlace, happy_hour=True)

However, if you attempt to create an instance in this manner now, you receive a DatabaseIntegrityError, saying that a Place object with that id already exists.

Attachments (1)

makechild.patch (2.6 KB) - added by korpios 7 years ago.

Download all attachments as: .zip

Change History (36)

comment:1 Changed 7 years ago by korpios

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

I have a Bazaar branch available that solves this issue:

In short, it adds a couple of new methods to QuerySet / Manager: prepare_child() and create_child(). For an existing Place p that isn't currently a Restaurant, you could do either of the following:

# p is an existing Place
r = Restaurant.objects.prepare_child(p)
r.cusine = "Sushi"
r.rating = 5

# or, alternately
r = Restaurant.objects.create_child(p, cusine="Sushi", rating=5)

While create_child() saves to the database, prepare_child() does not; the latter allows you to further alter the instance before saving.

I tried several alternate routes — supporting setting children directly from parents, supporting setting parents directly from children, a single unified child() method — and none worked cleanly. The unified child() method presented the issue of passing a save flag; there was no way to avoid a collision with a field named save in the keyword arguments.

I'll add an initial patch here based on the Bazaar repository for now; I still need to add tests. Any patches here will lag behind the Bazaar repository, as the latter is where my actual development takes place.

comment:2 Changed 7 years ago by korpios

  • Needs documentation set
  • Needs tests set

Changed 7 years ago by korpios

comment:3 Changed 7 years ago by korpios

  • Has patch set
  • Patch needs improvement set

comment:4 Changed 7 years ago by korpios

  • Cc korpios@… added

comment:5 Changed 7 years ago by ericholscher

  • milestone set to post-1.0
  • Triage Stage changed from Unreviewed to Design decision needed

comment:6 Changed 7 years ago by korpios

  • Cc korpios@… removed

comment:7 Changed 7 years ago by anonymous

  • milestone post-1.0 deleted

Milestone post-1.0 deleted

comment:8 Changed 6 years ago by kmtracey

#11618 was a dup.

comment:9 Changed 5 years ago by sirex

  • Cc sirexas@… added

comment:10 Changed 5 years ago by julien

  • Component changed from Core framework to Database layer (models, ORM)

comment:11 Changed 4 years ago by lukeplant

  • Severity set to Normal
  • Type set to Bug

comment:12 Changed 4 years ago by Alex

  • Easy pickings unset
  • Resolution set to needsinfo
  • Status changed from new to closed
  • UI/UX unset

Discussion with Jacob: closing as needsinfo, the modeling in the ticket does not seem like it *should* work, a model cannot be an instance of multiple subclasses.

comment:13 Changed 4 years ago by Nan

  • Resolution needsinfo deleted
  • Status changed from closed to reopened

Here's a more reasonable example...

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    place = models.OneToOneField(Place, parent_link=True, related_name='restaurant')
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()

Now, say we have a bunch of Place objects in the database, and a form where users can inform us what sort of business a particular Place is (OK, this is a contrived example, but I'm sure you can see that this could be a reasonable use case for some types of data).

# somewhere in
p = Place.objects.get(pk=1)
restaurant = Restaurant(**{
    'place': p,
    'serves_hot_dogs': False,
    'serves_pizza': True,

comment:14 Changed 4 years ago by aaugustin

  • Resolution set to wontfix
  • Status changed from reopened to closed

If multiple related objects are linked to the same object, they should have a foreign key to this object, not "extend" it; that would break the "instance" paradigm.

You can always use a Python mixin if you have related objects of different types, but want to share some methods.

comment:15 Changed 4 years ago by Nan

  • Resolution wontfix deleted
  • Status changed from closed to reopened

Please see my example immediately above. This is not about foreign keys or multiple related objects linked to the same object. It's about multi-table inheritance where the child data isn't always known at the time the parent object is created.

If you have a Place object that is not yet a Restaurant object, the ORM will not allow you to add the data to make it a Restaurant object.

comment:16 Changed 4 years ago by aaugustin

  • Resolution set to worksforme
  • Status changed from reopened to closed

Ah, I understand. This works:

p = Place.objects.get(pk=1)
restaurant = Restaurant(place_ptr=p, ...)

comment:17 Changed 4 years ago by bunyan

  • Resolution worksforme deleted
  • Status changed from closed to reopened

This leads to the behavior described in #11618 - the parent instance fields get overwritten.

comment:18 Changed 4 years ago by akaariai

  • Cc anssi.kaariainen@… added

If you want to extend an existing instance, you need to copy all fields of the parent to the new child manually. Unfortunately this can't be done in a generic way using only public APIs. Although ._meta is semi-public and doing

child = ChildModel(**childfields) # set child field values
for field in parent._meta.fields:
    setattr(child, field.attname, getattr(parent, field.attname)) # set child values from parent
child.parent_ptr = # not sure if this is strictly necessary, probably so...

or something along those lines should work (not tested). Now, I don't think this can be officially documented without making ._meta.fields part of the public API. I would have use for a helper method that does this.

comment:19 Changed 3 years ago by carbonXT

  • Cc mike@… added

comment:20 follow-up: Changed 3 years ago by Edward Askew <gnutrino@…>

There's actually an easier workaround for this:

parent = Place.objects.get(...)
child = Restaurant(place_ptr=parent,...)

#Overwrites the parents fields except the primary key with None (because the parent fields in child are not auto filled)

#Restores the parent fields to their previous values

#hit the database again to fill the proper values for the inherited fields
child = Restaurant.objects.get( 

comment:21 in reply to: ↑ 20 Changed 3 years ago by sammiestoel@…

There is an even easier work around not 100% sure if it works as expected yet though:

child = Restaurant(

Tested it myself for my use case as working.
Thanks to:

comment:22 Changed 3 years ago by loic84

I'm not sure it covers all bases but I use:

child = Restaurant(place_ptr=place)

It would be great to have an obvious and officially blessed way of doing this though.

comment:23 Changed 3 years ago by aaugustin

  • Status changed from reopened to new

comment:24 Changed 3 years ago by akaariai

  • Triage Stage changed from Design decision needed to Accepted

I think this makes sense. Unlink child but keep the parent and link child to existing parent would both be useful in some situatios.

comment:25 Changed 2 years ago by elektrrrus

  • Owner changed from nobody to elektrrrus
  • Status changed from new to assigned

comment:26 Changed 23 months ago by aaugustin

#21537 was a duplicate.

comment:27 Changed 22 months ago by pcompassion

I'd like this to be implemented as well. I have the following model relationships and it's just hard to do what django expects me to do when creating objects.

Thread has 1 main post.
Thread has many other posts
Post has 0-1 parent post.

There are multiple type of Threads.

It's just easier to work with when I can create BaseThread and extend it.

Besides, there are cases where I need to switch Thread types.
If it's possible to treat the base and extended separately, I can keep the base part, and create the extension part, and connect them.

comment:28 Changed 21 months ago by Reza Mohammadi <reza@…>

raw=True doesn't always work. raw is used for two different purposes:

The 'raw' argument is telling save_base not to save any parent models and
not to do any changes to the values before save. This is used by fixture loading.

For the purpose of this ticket, we want save_base ignore the first part but we need the second part to be done.

I think raw argument should be split to two different arguments, as its functionality is already separated. Not as a workaround for this bug, but to make the code more readable.

comment:29 Changed 21 months ago by Reza Mohammadi <reza@…>

  • Cc reza@… added

comment:30 Changed 20 months ago by juan64645@…

Hello! I am new in django. I have a problem like this.
I want to do something like this(in inheritance multi-table):


restaurant = Restaurant(place_ptr=place)

assuming that the attributes of restaurant hold null=True.
but Restaurant(place_ptr=place) returns nothing.

My vercion of django is 1.5
They say about this? Is there any alternative?
That is not to create a new restaurant and it was clear the place, because it is very ugly.

From already thank you very much!

comment:31 Changed 9 months ago by collinanderson

  • Cc cmawebsite@… added
  • Summary changed from Multi-table inheritance does not allow linking new instance of child model to existing parent model instance. to Multi-table inheritance: create child instance from existing parent

comment:32 Changed 3 weeks ago by ar45

  • Cc ar45 added

comment:33 Changed 3 weeks ago by ar45

Anything wrong with this implementation? (taken from Tom Tobin's branch referenced in this ticket)

Last edited 3 weeks ago by ar45 (previous) (diff)

comment:34 Changed 35 hours ago by timgraham

It's a bit difficult to review without tests. To get a review of the API design, it's a good idea to offer a high level overview on the DevelopersMailingList.

comment:35 Changed 9 hours ago by timgraham

  • Needs documentation unset
  • Summary changed from Multi-table inheritance: create child instance from existing parent to Multi-table inheritance: Add the ability create child instance from existing parent
  • Type changed from Bug to New feature
Note: See TracTickets for help on using tickets.
Back to Top