After uncompressing (if necessary), in the directory "...django_polymorphic", execute (on Unix-like systems):
sudo python setup.py install
Use PolymorphicModel instead of Django's models.Model, like so:
from polymorphic import PolymorphicModel
class Project(PolymorphicModel):
topic = models.CharField(max_length=30)
class ArtProject(Project):
artist = models.CharField(max_length=30)
class ResearchProject(Project):
supervisor = models.CharField(max_length=30)
All models inheriting from your polymorphic models will be polymorphic as well.
>>> Project.objects.create(topic="Department Party") >>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") >>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
>>> Project.objects.all() [ <Project: id 1, topic "Department Party">, <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">, <ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
use instance_of or not_instance_of for narrowing the result to specific subtypes:
>>> Project.objects.instance_of(ArtProject) [ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner"> ]
>>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject) [ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">, <ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
Polymorphic filtering: Get all projects where Mr. Turner is involved as an artist or supervisor (note the three underscores):
>>> Project.objects.filter( Q(ArtProject___artist = 'T. Turner') | Q(ResearchProject___supervisor = 'T. Turner') ) [ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">, <ResearchProject: id 4, topic "Color Use in Late Cubism", supervisor "T. Turner"> ]
This is basically all you need to know, as django_polymorphic mostly works fully automatic and just delivers the expected ("pythonic") results.
Note: In all example output, above and below, for a nicer and more informative output the ShowFieldType mixin has been used (documented below).
Django 1.1 (or later) and Python 2.4 or later. This code has been tested on Django 1.1 / 1.2 / 1.3 and Python 2.4.6 / 2.5.4 / 2.6.4 on Linux.
The repository (or tar file) contains a complete Django project that may be used for tests or experiments, without any installation needed.
To run the included test suite, in the directory "...django_polymorphic" execute:
./manage test polymorphic
The management command pcmd.py in the app pexp can be used for quick tests or experiments - modify this file (pexp/management/commands/pcmd.py) to your liking, then run:
./manage syncdb # db is created in /var/tmp/... (settings.py) ./manage pcmd
In the directory "...django_polymorphic", execute sudo python setup.py install.
Alternatively you can simply copy the polymorphic subdirectory (under "django_polymorphic") into your Django project dir (e.g. if you want to distribute your project with more 'batteries included').
If you want to run the test cases in polymorphic/tests.py, you need to add polymorphic to your INSTALLED_APPS setting.
Django's ContentType framework (django.contrib.contenttypes) needs to be listed in INSTALLED_APPS (usually it already is).
In the examples below, these models are being used:
from polymorphic import PolymorphicModel
class ModelA(PolymorphicModel):
field1 = models.CharField(max_length=10)
class ModelB(ModelA):
field2 = models.CharField(max_length=10)
class ModelC(ModelB):
field3 = models.CharField(max_length=10)
>>> ModelA.objects.instance_of(ModelB) . [ <ModelB: id 2, field1 (CharField), field2 (CharField)>, <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
In general, including or excluding parts of the inheritance tree:
ModelA.objects.instance_of(ModelB [, ModelC ...]) ModelA.objects.not_instance_of(ModelB [, ModelC ...])
You can also use this feature in Q-objects (with the same result as above):
>>> ModelA.objects.filter( Q(instance_of=ModelB) )
For example, cherrypicking objects from multiple derived classes anywhere in the inheritance tree, using Q objects (with the syntax: exact model name + three _ + field name):
>>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') ) . [ <ModelB: id 2, field1 (CharField), field2 (CharField)>, <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
Querysets could now be regarded as object containers that allow the aggregation of different object types, very similar to python lists - as long as the objects are accessed through the manager of a common base class:
>>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) . [ <ModelX: id 1, field_x (CharField)>, <ModelY: id 2, field_y (CharField)> ]
Relationship fields referring to polymorphic models work as expected: like polymorphic querysets they now always return the referred objects with the same type/class these were created and saved as.
E.g., if in your model you define:
field1 = OneToOneField(ModelA)
then field1 may now also refer to objects of type ModelB or ModelC.
A ManyToManyField example:
# The model holding the relation may be any kind of model, polymorphic or not
class RelatingModel(models.Model):
many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model
>>> o=RelatingModel.objects.create()
>>> o.many2many.add(ModelA.objects.get(id=1))
>>> o.many2many.add(ModelB.objects.get(id=2))
>>> o.many2many.add(ModelC.objects.get(id=3))
>>> o.many2many.all()
[ <ModelA: id 1, field1 (CharField)>,
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
Third party models can be used as polymorphic models without restrictions by subclassing them. E.g. using a third party model as the root of a polymorphic inheritance tree:
from thirdparty import ThirdPartyModel
class MyThirdPartyBaseModel(PolymorhpicModel, ThirdPartyModel):
pass # or add fields
Or instead integrating the third party model anywhere into an existing polymorphic inheritance tree:
class MyBaseModel(SomePolymorphicModel):
my_field = models.CharField(max_length=10)
class MyModelWithThirdParty(MyBaseModel, ThirdPartyModel):
pass # or add fields
If you insert .non_polymorphic() anywhere into the query chain, then django_polymorphic will simply leave out the final step of retrieving the real objects, and the manager/queryset will return objects of the type of the base class you used for the query, like vanilla Django would (ModelA in this example).
>>> qs=ModelA.objects.non_polymorphic().all() >>> qs [ <ModelA: id 1, field1 (CharField)>, <ModelA: id 2, field1 (CharField)>, <ModelA: id 3, field1 (CharField)> ]
There are no other changes in the behaviour of the queryset. For example, enhancements for filter() or instance_of() etc. still work as expected. If you do the final step yourself, you get the usual polymorphic result:
>>> ModelA.objects.get_real_instances(qs) [ <ModelA: id 1, field1 (CharField)>, <ModelB: id 2, field1 (CharField), field2 (CharField)>, <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
The queryset enhancements (e.g. instance_of) only work as arguments to the member functions of a polymorphic queryset. Occationally it may be useful to be able to use Q objects with these enhancements in other places. As Django doesn't understand these enhanced Q objects, you need to transform them manually into normal Q objects before you can feed them to a Django queryset or function:
normal_q_object = ModelA.translate_polymorphic_Q_object( Q(instance_of=Model2B) )
This function cannot be used at model creation time however (in models.py), as it may need to access the ContentTypes database table.
In order to get the output as seen in all examples here, you need to use the ShowFieldType class mixin:
from polymorphic import PolymorphicModel, ShowFieldType
class ModelA(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10)
You may also use ShowFieldContent or ShowFieldTypeAndContent to display additional information when printing querysets (or converting them to text).
When showing field contents, they will be truncated to 20 characters. You can modify this behaviour by setting a class variable in your model like this:
class ModelA(ShowFieldType, PolymorphicModel):
polymorphic_showfield_max_field_width = 20
...
Similarly, pre-V1.0 output formatting can be re-estated by using polymorphic_showfield_old_format = True.
A nice feature of Django is the possibility to define one's own custom object managers. This is fully supported with django_polymorphic: For creating a custom polymorphic manager class, just derive your manager from PolymorphicManager instead of models.Manager. As with vanilla Django, in your model class, you should explicitly add the default manager first, and then your custom manager:
from polymorphic import PolymorphicModel, PolymorphicManager
class TimeOrderedManager(PolymorphicManager):
def get_query_set(self):
qs = super(TimeOrderedManager,self).get_query_set()
return qs.order_by('-start_date') # order the queryset
def most_recent(self):
qs = self.get_query_set() # get my ordered queryset
return qs[:10] # limit => get ten most recent entries
class Project(PolymorphicModel):
objects = PolymorphicManager() # add the default polymorphic manager first
objects_ordered = TimeOrderedManager() # then add your own manager
start_date = DateTimeField() # project start is this date/time
The first manager defined ('objects' in the example) is used by Django as automatic manager for several purposes, including accessing related objects. It must not filter objects and it's safest to use the plain PolymorphicManager here.
Polymorphic models inherit/propagate all managers from their base models, as long as these are polymorphic. This means that all managers defined in polymorphic base models continue to work as expected in models inheriting from this base model:
from polymorphic import PolymorphicModel, PolymorphicManager
class TimeOrderedManager(PolymorphicManager):
def get_query_set(self):
qs = super(TimeOrderedManager,self).get_query_set()
return qs.order_by('-start_date') # order the queryset
def most_recent(self):
qs = self.get_query_set() # get my ordered queryset
return qs[:10] # limit => get ten most recent entries
class Project(PolymorphicModel):
objects = PolymorphicManager() # add the default polymorphic manager first
objects_ordered = TimeOrderedManager() # then add your own manager
start_date = DateTimeField() # project start is this date/time
class ArtProject(Project): # inherit from Project, inheriting its fields and managers
artist = models.CharField(max_length=30)
ArtProject inherited the managers objects and objects_ordered from Project.
ArtProject.objects_ordered.all() will return all art projects ordered regarding their start time and ArtProject.objects_ordered.most_recent() will return the ten most recent art projects. .
The PolymorphicManager class accepts one initialization argument, which is the queryset class the manager should use. Just as with vanilla Django, you may define your own custom queryset classes. Just use PolymorphicQuerySet instead of Django's QuerySet as the base class:
from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet
class MyQuerySet(PolymorphicQuerySet):
def my_queryset_method(...):
...
class MyModel(PolymorphicModel):
my_objects=PolymorphicManager(MyQuerySet)
...
The current implementation is rather simple and does not use any custom SQL or Django DB layer internals - it is purely based on the standard Django ORM.
Specifically, the query:
result_objects = list( ModelA.objects.filter(...) )
performs one SQL query to retrieve ModelA objects and one additional query for each unique derived class occurring in result_objects. The best case for retrieving 100 objects is 1 SQL query if all are class ModelA. If 50 objects are ModelA and 50 are ModelB, then two queries are executed. The pathological worst case is 101 db queries if result_objects contains 100 different object types (with all of them subclasses of ModelA).
Usually, when Django users create their own polymorphic ad-hoc solution without a tool like django_polymorphic, this usually results in a variation of
result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]
which has very bad performance, as it introduces one additional SQL query for every object in the result which is not of class BaseModel.
Compared to these solutions, django_polymorphic has the advantage that it only needs one sql request per object type, and not per object.
Current relational DBM systems seem to have general problems with the SQL queries produced by object relational mappers like the Django ORM, if these use multi-table inheritance like Django's ORM does. The "inner joins" in these queries can perform very badly. This is independent of django_polymorphic and affects all uses of multi table Model inheritance.
Concrete benchmark results are forthcoming (please see discussion forum).
Please also see this post (and comments) from Jacob Kaplan-Moss.
Django_polymorphic works well for a considerable number of users now, and no major problems have shown up for many months. The API can be considered stable beginning with the V1.0 release.