Django supports inherited model declarations through the sub-classing of model classes in python code. However, the use case for these inherited models is to only deal with the sub-classes themselves, and not to consider the role of sub-classes in the context of their super-class. The aim of djeneralize is to provide a way of mixing specializations of a general model case and work with both the general case and the special cases interchangeably.
Note
Before reading through the examples, it is a good idea to be familiar with some of the Terminology used in this documentation.
Let us consider the following example of model declarations which are contained within the writing app:
from django.db import models
from djeneralize.models import BaseGeneralizationModel
#{ General model
class WritingImplement(BaseGeneralizationModel):
name = models.CharField(max_length=30)
length = models.IntegerField()
def __unicode__(self):
return self.name
#}
#{ Direct children of WritingImplement, i.e. first specializtion
class Pencil(WritingImplement):
lead = models.CharField(max_length=2) # i.e. HB, B2, H5
class Meta:
specialization = 'pencil'
class Pen(WritingImplement):
ink_colour = models.CharField(max_length=30)
class Meta:
specialization = 'pen'
#}
#{ Grand-children of WritingImplement, i.e. second degree of specialization
class FountainPen(Pen):
nib_width = models.DecimalField(max_digits=3, decimal_places=2)
class Meta:
specialization = 'fountain_pen'
class BallPointPen(Pen):
replaceable_insert = models.BooleanField(default=False)
class Meta:
specialization = 'ballpoint_pen'
#}
Django handles the creation of the related tables for these models very nicely. If we execute python manage.py sqlall writing, we get the following with a Postgres backend):
BEGIN;
CREATE TABLE "writing_writingimplement" (
"id" integer NOT NULL PRIMARY KEY,
"specialization_type" text NOT NULL,
"name" varchar(30) NOT NULL,
"length" integer NOT NULL
)
;
CREATE TABLE "writing_pencil" (
"writingimplement_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "writing_writingimplement" ("id"),
"lead" varchar(2) NOT NULL
)
;
CREATE TABLE "writing_pen" (
"writingimplement_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "writing_writingimplement" ("id"),
"ink_colour" varchar(30) NOT NULL
)
;
CREATE TABLE "writing_fountainpen" (
"pen_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "writing_pen" ("writingimplement_ptr_id"),
"nib_width" decimal NOT NULL
)
;
CREATE TABLE "writing_ballpointpen" (
"pen_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "writing_pen" ("writingimplement_ptr_id"),
"replaceable_insert" bool NOT NULL
)
;
CREATE INDEX "writing_writingimplement_524e029a" ON "writing_writingimplement" ("specialization_type");
COMMIT;
If we inspect the available fields on the general model (WritingImplement) and its sub-classes, we find that Django has set-up all the required fields respecting the models’ inheritance:
>>> from writing.models import *
>>> WritingImplement._meta.get_all_field_names()
['id', 'length', 'name', 'pen', 'pencil', 'specialization_type']
>>> Pen._meta.get_all_field_names()
['ballpointpen', 'fountainpen', 'id', 'ink_colour', 'length', 'name', 'specialization_type', 'writingimplement_ptr']
>>> Pencil._meta.get_all_field_names()
['id', 'lead', 'length', 'name', 'specialization_type', 'writingimplement_ptr']
>>> FountainPen._meta.get_all_field_names()
['id', 'ink_colour', 'length', 'name', 'nib_width', 'pen_ptr', 'specialization_type', 'writingimplement_ptr']
>>> BallPointPen._meta.get_all_field_names()
['id', 'ink_colour', 'length', 'name', 'pen_ptr', 'replaceable_insert', 'specialization_type', 'writingimplement_ptr']
So far, so good. When we have the general case instances, we get just the general fields and when we’re using specialized model instances, we have all the specialized fields. However, ...
This specificity and generality are all well and good, but we we have a collection of writing implements, but want to consider exactly which sub-class of a writing implement these are, we don’t get an assistance from Django:
>>> general_pen = Pen.objects.create(length=10, name='General pen', ink_colour='Black')
>>> fountain_pen = FountainPen.objects.create(length=15, name='Fountain pen', ink_colour='Blue', nib_width='1.2')
>>> ballpoint_pen = BallPointPen.objects.create(length=9, name='Ballpoint pen', ink_colour='Green', replaceable_insert=False)
>>> pencil = Pencil.objects.create(length=12, name='Pencil', lead='HB')
>>> WritingImplement.objects.all()
[<WritingImplement: General pen>, <WritingImplement: Fountain pen>, <WritingImplement: Ballpoint pen>, <WritingImplement: Pencil>]
>>> Pen.objects.all()
[<Pen: General pen>, <Pen: Fountain pen>, <Pen: Ballpoint pen>]
>>> FountainPen.objects.all()
[<FountainPen: Fountain pen>]
These lists are all correct in terms of the model instances which are returned, but wouldn’t it be great if we could query all our writing implements and get back a queryset which, when iterated over gave us a instances or the specializations. With djeneralize this is a possibility:
>>> WritingImplement.specializations.all()
[<FountainPen: Fountain pen>, <Pen: General pen>, <BallPointPen: Ballpoint pen>, <Pencil: Pencil>]
Additionallty, if we have a general case model instance, we can get its specialization:
>>> wi = WritingImplement.objects.get(length=9)
>>> wi
<WritingImplement: Ballpoint pen>
>>> wi.get_as_specialization()
<BallPointPen: Ballpoint pen>
That’s about all there is to djeneralize. To make this all work, take a look at Defnining models with specializations.