|Version 3 (modified by jkocherhans, 9 years ago) (diff)|
The previous version of this page is obsolete due to the model syntax change. Additionally, there's now a better reference for this topic:
magic-removal: Model Inheritance
This is a proposal for how subclassing should work in Django. There a lot of details to get right, so this proposal should be very specific and detailed. Most of the ideas here come from the thread linked below:
For subclassing, there are 3 main issues:
- How do we model the relations in SQL?
- How do joins work?
- How does the API work?
Note that I have only provided examples for single inheritance here. Is multiple inheritance worth supporting?
The examples will use the following models:
class Place(models.Model): name = models.CharField(maxlength=50) class Restaurant(Place): description = models.TextField() class ItalianRestaurant(Restaurant): has_decent_gnocci = models.BooleanField()
1. Modeling parent relations in SQL?
- Foreign key to parent table named $(parenttable)_id
- Foreign key to parent table named 'parent_id'
- 'id' of parent row equals 'id' of child row
- and 2. would take up part of the objects namespace. With 1, you couldn't have attributes named $(parenttable)_id or 'parent_id' respectively.
With option 3, auto-incrementing might be harder, but it makes more sense to have 1 id across the board. I think one would expect Place.objects.get(2) to return the "same" object as Restaurant.objects.get(2).
Here's an example of option 1:
CREATE TABLE "myapp_place" ( "id" integer NOT NULL PRIMARY KEY, "name" varchar(50) NOT NULL ); CREATE TABLE "myapp_restaurant" ( "id" integer NOT NULL PRIMARY KEY, "place_id" integer NOT NULL REFERENCES "myapp_place" ("id"), "description" text NOT NULL ); CREATE TABLE "myapp_italianrestaurant" ( "id" integer NOT NULL PRIMARY KEY, "restaurant_id" integer NOT NULL REFERENCES "myapp_restaurant" ("id"), "has_decent_gnocci" bool NOT NULL );
and option 3:
CREATE TABLE "myapp_place" ( "id" integer NOT NULL PRIMARY KEY, "name" varchar(50) NOT NULL ); CREATE TABLE "myapp_restaurant" ( "id" integer NOT NULL PRIMARY KEY, /* Could this reference myapp_place's id too? Doubtful. :( */ "description" text NOT NULL ); CREATE TABLE "myapp_italianrestaurant" ( "id" integer NOT NULL PRIMARY KEY, /* Ditto for referencing myapp_restaurant's id */ "has_decent_gnocci" bool NOT NULL );
2. Modeling joins in SQL
When we want a list of ItalianRestaurants, we obviously need all the fields from myapp_restaurant and myapp_place as well. This could be accomplished by left joins. It would look something like this:
SELECT ... FROM myapp_italianrestaurant as ir LEFT JOIN myapp_restaurant as r ON ir.restaurant_id=r.id LEFT JOIN myapp_place as p ON r.place_id=p.id
But what if we want a list of Places, what should we do? We can either just get the places:
SELECT ... FROM myapp_place
Or we can get everything with left joins (this allows the iterator to return objects of the appropriate type, rather than just a bunch of Places):
SELECT ... FROM myapp_place as p LEFT JOIN myapp_restaurant as r ON r.place_id=p.id LEFT JOIN myapp_italianrestaurant as ir ON ir.restaurant_id=r.id
Imagine we have more than one subclass of Place though. The join clause and the column list would get pretty hefty. This could obviously get unmanageable pretty quickly.
Another option is to lazily load objects like Restaurant and ItalianRestaurant while we're iterating over Place.objects.all(), but that requires a lot of database queries. Either way, doing this will be expensive, and api should reflect that. You're much better off just using Places fields if you are going to iterate over Place.objects.all()
The following API examples assume we have created these objects:
p = Place(name='My House') r = Restaurant(name='Road Kill Cafe', description='Yuck!') ir = ItalianRestaurant(name='Luigis', description='Something', has_decent_gnocci=False)
For the following examples, assume Place.objects.get(2) returns r and Place.objects.get(3) returns ir.
|D.||Place.objects.get(2).description||'Yuck!' or AttributeError?|
Change the current usage of subclassing
class MyArticle(Article): ...fields... class META: module_name = 'my_articles' remove_fields = ...some fields...
would change to:
class MyArticle(meta.Model): ...fields... class META: copy_from = Article remove_fields = ...some fields...