Introduction¶
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
from djeneralize.fields import SpecializedForeignKey
#{ General model
class WritingImplement(BaseGeneralizationModel):
name = models.CharField(max_length=30)
length = models.IntegerField()
holder = SpecializedForeignKey(
'WritingImplementHolder', null=True, blank=True)
def __unicode__(self):
return self.name
#{ Direct children of WritingImplement, i.e. first specialization
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'
#{ Writing implement holders general model
class WritingImplementHolder(BaseGeneralizationModel):
name = models.CharField(max_length=30)
def __unicode__(self):
return self.name
#{ Writing implement holders specializations
class StationaryCupboard(WritingImplementHolder):
volume = models.FloatField()
class Meta:
specialization = 'stationary_cupboard'
class PencilCase(WritingImplementHolder):
colour = models.CharField(max_length=30)
class Meta:
specialization = 'pencil_case'
#}
What we get from Django¶
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
PostgreSQL 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,
"holder_id" integer
)
;
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 TABLE "writing_writingimplementholder" (
"id" integer NOT NULL PRIMARY KEY,
"specialization_type" text NOT NULL,
"name" varchar(30) NOT NULL
)
;
CREATE TABLE "writing_stationarycupboard" (
"writingimplementholder_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "writing_writingimplementholder" ("id"),
"volume" real NOT NULL
)
;
CREATE TABLE "writing_pencilcase" (
"writingimplementholder_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "writing_writingimplementholder" ("id"),
"colour" varchar(30) NOT NULL
)
;
CREATE INDEX "writing_writingimplement_524e029a" ON "writing_writingimplement" ("specialization_type");
CREATE INDEX "writing_writingimplement_1400a0f4" ON "writing_writingimplement" ("holder_id");
CREATE INDEX "writing_writingimplementholder_524e029a" ON "writing_writingimplementholder" ("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()
['holder', 'id', 'length', 'name', 'pen', 'pencil', 'specialization_type']
>>> Pen._meta.get_all_field_names()
['ballpointpen', 'fountainpen', 'holder', 'id', 'ink_colour', 'length', 'name', 'specialization_type', 'writingimplement_ptr']
>>> Pencil._meta.get_all_field_names()
['holder', 'id', 'lead', 'length', 'name', 'specialization_type', 'writingimplement_ptr']
>>> FountainPen._meta.get_all_field_names()
['holder', 'id', 'ink_colour', 'length', 'name', 'nib_width', 'pen_ptr', 'specialization_type', 'writingimplement_ptr']
>>> BallPointPen._meta.get_all_field_names()
['holder', '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, ...
What djeneralize does to extend Django’s functionality¶
This specificity and generality are all well and good, but if we have a collection of writing implements and want to consider exactly to which sub-classes of WritingImplement they belong, then we don’t get any 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 instances of the
specializations. With djeneralize
this is a possibility:
>>> WritingImplement.specializations.all()
[<FountainPen: Fountain pen>, <Pen: General pen>, <BallPointPen: Ballpoint pen>, <Pencil: Pencil>]
Additionally, 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>
Foreign keys¶
To make foreign keys return specializations, simply use the
djeneralize.fields.SpecializedForeignKey
field in your model and then
references will automatically return the specialization:
>>> from writing.models import *
>>> stationary_cupboard = StationaryCupboard.objects.create(name='Office cupboard', volume=1.2)
>>> pencil = Pencil.objects.create(length=12, name='Pencil', lead='HB', holder=stationary_cupboard)
>>> pencil.holder
<StationaryCupboard: Office cupboard>
That’s about all there is to djeneralize
. To make this all work, take a
look at Defining models with specializations.