updated docs, and moved them to DOCS.rst and README.rst
parent
e3346bd4fb
commit
ba9a0b1302
|
|
@ -0,0 +1,4 @@
|
||||||
|
django_polymorphic was initially created by Bert Constantin in 2009/2010.
|
||||||
|
|
||||||
|
If you contributed code, test cases etc., your name should
|
||||||
|
appear here as well.
|
||||||
|
|
@ -0,0 +1,434 @@
|
||||||
|
===============================
|
||||||
|
Fully Polymorphic Django Models
|
||||||
|
===============================
|
||||||
|
|
||||||
|
|
||||||
|
'polymorphic.py' is an add-on module that adds automatic
|
||||||
|
polymorphism to the Django model inheritance system.
|
||||||
|
|
||||||
|
The effect is: For enabled models, objects retrieved from the
|
||||||
|
database are always delivered just as they were created and saved,
|
||||||
|
with the same type/class and fields - regardless how they are
|
||||||
|
retrieved. The resulting querysets are polymorphic, i.e. may deliver
|
||||||
|
objects of several different types in a single query result.
|
||||||
|
|
||||||
|
Please see the examples below as they demonstrate this best.
|
||||||
|
|
||||||
|
Please note that this module is still very experimental. See below for
|
||||||
|
current restrictions, caveats, and performance implications.
|
||||||
|
|
||||||
|
|
||||||
|
Installation / Testing
|
||||||
|
======================
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
Django 1.1 (or later) and Python 2.5 (or later). This code has been tested
|
||||||
|
on Django 1.1.1 / 1.2 alpha and Python 2.5.4 / 2.6.4 on Linux.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
|
||||||
|
The repository (or tar file) contains a complete Django project
|
||||||
|
that may be used for tests or experiments.
|
||||||
|
|
||||||
|
To run the included test suite, execute::
|
||||||
|
|
||||||
|
./manage test poly
|
||||||
|
|
||||||
|
'management/commands/polycmd.py' can be used for experiments
|
||||||
|
- modify this file to your liking, then run::
|
||||||
|
|
||||||
|
./manage syncdb # db is created in /var/tmp/... (settings.py)
|
||||||
|
./manage polycmd
|
||||||
|
|
||||||
|
Using polymorphic models in your own projects
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
Copy polymorphic.py (from the 'poly' dir) into a directory from where
|
||||||
|
you can import it, like your app directory (where your models.py and
|
||||||
|
views.py files live).
|
||||||
|
|
||||||
|
|
||||||
|
Defining Polymorphic Models
|
||||||
|
===========================
|
||||||
|
|
||||||
|
To make models polymorphic, use PolymorphicModel instead of Django's
|
||||||
|
models.Model as the superclass of your base model. All models
|
||||||
|
inheriting from your base class will be polymorphic as well::
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
Using Polymorphic Models
|
||||||
|
========================
|
||||||
|
|
||||||
|
Most of Django's standard ORM functionality is available
|
||||||
|
and works as expected:
|
||||||
|
|
||||||
|
Create some objects
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
>>> ModelA.objects.create(field1='A1')
|
||||||
|
>>> ModelB.objects.create(field1='B1', field2='B2')
|
||||||
|
>>> ModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
||||||
|
|
||||||
|
Query results are polymorphic
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
>>> ModelA.objects.all()
|
||||||
|
.
|
||||||
|
[ <ModelA: id 1, field1 (CharField)>,
|
||||||
|
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||||
|
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||||
|
|
||||||
|
Filtering for classes (equivalent to python's isinstance() ):
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
>>> 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 ...])
|
||||||
|
|
||||||
|
Polymorphic filtering (for fields in derived classes)
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
For example, cherrypicking objects from multiple derived classes
|
||||||
|
anywhere in the inheritance tree, using Q objects (with the
|
||||||
|
slightly enhanced 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)> ]
|
||||||
|
|
||||||
|
Combining Querysets of different types/models
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
Querysets may 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)> ]
|
||||||
|
|
||||||
|
Using Third Party Models (without modifying them)
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
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 MyThirdPartyModel(PolymorhpicModel, ThirdPartyModel):
|
||||||
|
pass # or add fields
|
||||||
|
|
||||||
|
Or instead integrating the third party model anywhere into an
|
||||||
|
existing polymorphic inheritance tree::
|
||||||
|
|
||||||
|
class MyModel(SomePolymorphicModel):
|
||||||
|
my_field = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
class MyModelWithThirdParty(MyModel, ThirdPartyModel):
|
||||||
|
pass # or add fields
|
||||||
|
|
||||||
|
ManyToManyField, ForeignKey, OneToOneField
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
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)> ]
|
||||||
|
|
||||||
|
Non-Polymorphic Queries
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
>>> ModelA.base_objects.all()
|
||||||
|
.
|
||||||
|
[ <ModelA: id 1, field1 (CharField)>,
|
||||||
|
<ModelA: id 2, field1 (CharField)>,
|
||||||
|
<ModelA: id 3, field1 (CharField)> ]
|
||||||
|
|
||||||
|
Each polymorphic model has 'base_objects' defined as a normal
|
||||||
|
Django manager. Of course, arbitrary custom managers may be
|
||||||
|
added to the models as well.
|
||||||
|
|
||||||
|
|
||||||
|
Custom Managers, Querysets & Inheritance
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Using a Custom Manager
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
For creating a custom polymorphic manager class, derive your manager
|
||||||
|
from PolymorphicManager instead of models.Manager. In your model
|
||||||
|
class, explicitly add the default manager first, and then your
|
||||||
|
custom manager::
|
||||||
|
|
||||||
|
class MyOrderedManager(PolymorphicManager):
|
||||||
|
def get_query_set(self):
|
||||||
|
return super(MyOrderedManager,self).get_query_set().order_by('some_field')
|
||||||
|
|
||||||
|
class MyModel(PolymorphicModel):
|
||||||
|
objects = PolymorphicManager() # add the default polymorphic manager first
|
||||||
|
ordered_objects = MyOrderedManager() # then add your own manager
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Manager Inheritance / Propagation
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Polymorphic models unconditionally inherit all managers from their
|
||||||
|
base models (as long as these are polymorphic).
|
||||||
|
|
||||||
|
An example (inheriting from MyModel above)::
|
||||||
|
|
||||||
|
class MyModel2(MyModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Managers inherited from MyModel, delivering MyModel2 objects (including MyModel2 subclass objects)
|
||||||
|
>>> MyModel2.objects.all()
|
||||||
|
>>> MyModel2.ordered_objects.all()
|
||||||
|
|
||||||
|
Perhaps a more correct way to describe this: With polymorphic models the
|
||||||
|
managers are always fully propagated from all polymorphic base models
|
||||||
|
(as strictly speaking all managers are always inherited with Django models).
|
||||||
|
|
||||||
|
|
||||||
|
Using a Custom Queryset Class
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The PolymorphicManager class accepts one initialization argument,
|
||||||
|
which is the queryset class the manager should use. A custom
|
||||||
|
custom queryset class can be defined and used like this::
|
||||||
|
|
||||||
|
class MyQuerySet(PolymorphicQuerySet):
|
||||||
|
def my_queryset_method(...):
|
||||||
|
...
|
||||||
|
|
||||||
|
class MyModel(PolymorphicModel):
|
||||||
|
my_objects=PolymorphicManager(MyQuerySet)
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
Performance Considerations
|
||||||
|
==========================
|
||||||
|
|
||||||
|
The current implementation is pretty simple and does not use any
|
||||||
|
custom sql - it is purely based on the Django ORM. Right now 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 db query if all are
|
||||||
|
class ModelA. If 50 objects are ModelA and 50 are ModelB, then two
|
||||||
|
queries are executed. If result_objects contains only the base model
|
||||||
|
type (ModelA), the polymorphic models are just as efficient as plain
|
||||||
|
Django models (in terms of executed queries). The pathological worst
|
||||||
|
case is 101 db queries if result_objects contains 100 different
|
||||||
|
object types (with all of them subclasses of ModelA).
|
||||||
|
|
||||||
|
Performance ist relative: when Django users create their own
|
||||||
|
polymorphic ad-hoc solution (without a module like polymorphic.py),
|
||||||
|
this usually results in a variation of ::
|
||||||
|
|
||||||
|
result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]
|
||||||
|
|
||||||
|
which of has really bad performance. Relative to this, the
|
||||||
|
performance of the current polymorphic.py is pretty good.
|
||||||
|
It may well be efficient enough for the majority of use cases.
|
||||||
|
|
||||||
|
Chunking: The implementation always requests objects in chunks of
|
||||||
|
size Polymorphic_QuerySet_objects_per_request. This limits the
|
||||||
|
complexity/duration for each query, including the pathological cases.
|
||||||
|
|
||||||
|
|
||||||
|
Possible Optimizations
|
||||||
|
======================
|
||||||
|
|
||||||
|
PolymorphicQuerySet can be optimized to require only one SQL query
|
||||||
|
for the queryset evaluation and retrieval of all objects.
|
||||||
|
|
||||||
|
Basically, what ist needed is a possibility to pull in the fields
|
||||||
|
from all relevant sub-models with one sql query. However, some deeper
|
||||||
|
digging into the Django database layer will be required in order to
|
||||||
|
make this happen.
|
||||||
|
|
||||||
|
A viable option might be to get the sql query from the QuerySet
|
||||||
|
(probably from django.db.models.sql.compiler.SQLCompiler.as_sql),
|
||||||
|
making sure that all necessary joins are done, and then doing a
|
||||||
|
custom SQL request from there (like in SQLCompiler.execute_sql).
|
||||||
|
|
||||||
|
An optimized version could fall back to the current ORM-only
|
||||||
|
implementation for all non-SQL databases.
|
||||||
|
|
||||||
|
SQL Complexity
|
||||||
|
--------------
|
||||||
|
|
||||||
|
With only one sql query, one sql join for each possible subclass
|
||||||
|
would be needed (BaseModel.__subclasses__(), recursively).
|
||||||
|
With two sql queries, the number of joins could be reduced to the
|
||||||
|
number of actuallly occurring subclasses in the result. A final
|
||||||
|
implementation might want to use one query only if the number of
|
||||||
|
possible subclasses (and therefore joins) is not too large, and
|
||||||
|
two queries otherwise (using the first query to determine the
|
||||||
|
actually occurring subclasses, reducing the number of joins for
|
||||||
|
the second).
|
||||||
|
|
||||||
|
A relatively large number of joins may be needed in both cases,
|
||||||
|
which raises concerns regarding the efficiency of these database
|
||||||
|
queries. It is currently unclear however, how many model classes
|
||||||
|
will actually be involved in typical use cases - the total number
|
||||||
|
of classes in the inheritance tree as well as the number of distinct
|
||||||
|
classes in query results. It may well turn out that the increased
|
||||||
|
number of joins is no problem for the DBMS in all realistic use
|
||||||
|
cases. Alternatively, if the sql query execution time is
|
||||||
|
significantly longer even in common use cases, this may still be
|
||||||
|
acceptable in exchange for the added functionality.
|
||||||
|
|
||||||
|
Let's not forget that all of the above is just about optimizations.
|
||||||
|
The current simplistic implementation already works well - perhaps
|
||||||
|
well enough for the majority of applications.
|
||||||
|
|
||||||
|
|
||||||
|
Loose Ends
|
||||||
|
==========
|
||||||
|
|
||||||
|
Currently Unsupported Queryset Methods
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
+ aggregate() probably makes only sense in a purely non-OO/relational
|
||||||
|
way. So it seems an implementation would just fall back to the
|
||||||
|
Django vanilla equivalent.
|
||||||
|
|
||||||
|
+ annotate(): The current '_get_real_instances' would need minor
|
||||||
|
enhancement.
|
||||||
|
|
||||||
|
+ defer() and only(): Full support, including slight polymorphism
|
||||||
|
enhancements, seems to be straighforward
|
||||||
|
(depends on '_get_real_instances').
|
||||||
|
|
||||||
|
+ extra(): Does not really work with the current implementation of
|
||||||
|
'_get_real_instances'. It's unclear if it should be supported.
|
||||||
|
|
||||||
|
+ select_related(): This would probably need Django core support
|
||||||
|
for traversing the reverse model inheritance OneToOne relations
|
||||||
|
with Django's select_related(), e.g.:
|
||||||
|
*select_related('modela__modelb__foreignkeyfield')*.
|
||||||
|
Also needs more thought/investigation.
|
||||||
|
|
||||||
|
+ distinct() needs more thought and investigation as well
|
||||||
|
|
||||||
|
+ values() & values_list(): Implementation seems to be mostly
|
||||||
|
straighforward
|
||||||
|
|
||||||
|
|
||||||
|
Restrictions & Caveats
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
+ Diamond shaped inheritance: There seems to be a general problem
|
||||||
|
with diamond shaped multiple model inheritance with Django models
|
||||||
|
(tested with V1.1).
|
||||||
|
An example is here: http://code.djangoproject.com/ticket/10808.
|
||||||
|
This problem is aggravated when trying to enhance models.Model
|
||||||
|
by subclassing it instead of modifying Django core (as we do here
|
||||||
|
with PolymorphicModel).
|
||||||
|
|
||||||
|
+ The name and appname of the leaf model is stored in the base model
|
||||||
|
(the base model directly inheriting from PolymorphicModel).
|
||||||
|
If a model or an app is renamed, then these fields need to be
|
||||||
|
corrected too, if the db content should stay usable after the rename.
|
||||||
|
Aside from this, these two fields should probably be combined into
|
||||||
|
one field (more db/sql efficiency)
|
||||||
|
|
||||||
|
+ For all objects that are not instances of the base class type, but
|
||||||
|
instances of a subclass, the base class fields are currently
|
||||||
|
transferred twice from the database (an artefact of the current
|
||||||
|
implementation's simplicity).
|
||||||
|
|
||||||
|
+ __getattribute__ hack: For base model inheritance back relation
|
||||||
|
fields (like basemodel_ptr), as well as implicit model inheritance
|
||||||
|
forward relation fields, Django internally tries to use our
|
||||||
|
polymorphic manager/queryset in some places, which of course it
|
||||||
|
should not. Currently this is solved with hackish __getattribute__
|
||||||
|
in PolymorphicModel. A minor patch to Django core would probably
|
||||||
|
get rid of that.
|
||||||
|
|
||||||
|
In General
|
||||||
|
----------
|
||||||
|
|
||||||
|
It is important to consider that this code is still very new and experimental.
|
||||||
|
|
||||||
|
It has, however, been integrated into one larger system where all seems to work flawlessly
|
||||||
|
so far. A small number of people tested this code for their purposes and reported that it
|
||||||
|
works well for them.
|
||||||
|
|
||||||
|
Right now this module is suitable only for the more enterprising early adopters.
|
||||||
|
|
||||||
|
|
||||||
|
Links
|
||||||
|
=====
|
||||||
|
|
||||||
|
- http://code.djangoproject.com/wiki/ModelInheritance
|
||||||
|
- http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html
|
||||||
|
- http://www.djangosnippets.org/snippets/1031/
|
||||||
|
- http://www.djangosnippets.org/snippets/1034/
|
||||||
|
- http://groups.google.com/group/django-developers/browse_frm/thread/7d40ad373ebfa912/a20fabc661b7035d?lnk=gst&q=model+inheritance+CORBA#a20fabc661b7035d
|
||||||
|
- http://groups.google.com/group/django-developers/browse_thread/thread/9bc2aaec0796f4e0/0b92971ffc0aa6f8?lnk=gst&q=inheritance#0b92971ffc0aa6f8
|
||||||
|
- http://groups.google.com/group/django-developers/browse_thread/thread/3947c594100c4adb/d8c0af3dacad412d?lnk=gst&q=inheritance#d8c0af3dacad412d
|
||||||
|
- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/b76c9d8c89a5574f
|
||||||
|
- http://peterbraden.co.uk/article/django-inheritance
|
||||||
|
- http://www.hopelessgeek.com/2009/11/25/a-hack-for-multi-table-inheritance-in-django
|
||||||
|
- http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982
|
||||||
|
- http://stackoverflow.com/questions/1581024/django-inheritance-how-to-have-one-method-for-all-subclasses
|
||||||
|
- http://groups.google.com/group/django-users/browse_thread/thread/cbdaf2273781ccab/e676a537d735d9ef?lnk=gst&q=polymorphic#e676a537d735d9ef
|
||||||
|
- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/bc18c18b2e83881e?lnk=gst&q=model+inheritance#bc18c18b2e83881e
|
||||||
|
- http://code.djangoproject.com/ticket/10808
|
||||||
|
- http://code.djangoproject.com/ticket/7270
|
||||||
|
|
||||||
4
LICENSE
4
LICENSE
|
|
@ -1,4 +1,6 @@
|
||||||
Copyright (c) 2009 by Bert Constantin and individual contributors.
|
Copyright (c) 2009 or later by the individual contributors.
|
||||||
|
Please see the AUTHORS file.
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
||||||
485
README.rst
485
README.rst
|
|
@ -1,474 +1,39 @@
|
||||||
|
|
||||||
===============================
|
===============================
|
||||||
Fully Polymorphic Django Models
|
Fully Polymorphic Django Models
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
|
|
||||||
Overview
|
What it Does
|
||||||
========
|
============
|
||||||
|
|
||||||
'polymorphic.py' is an add-on module that adds fully automatic
|
If ``ArtProject`` and ``ResearchProject`` inherit from the model ``Project``::
|
||||||
polymorphism to the Django model inheritance system.
|
|
||||||
|
|
||||||
The effect is: For enabled models, objects retrieved from the
|
>>> Project.objects.all()
|
||||||
database are always delivered just as they were created and saved,
|
.
|
||||||
with the same type/class and fields - regardless how they are
|
[ <Project: id 1, topic: "John's Gathering">,
|
||||||
retrieved. The resulting querysets are polymorphic, i.e. may deliver
|
<ArtProject: id 2, topic: "Sculpting with Tim", artist: "T. Turner">,
|
||||||
|
<ResearchProject: id 3, topic: "Swallow Aerodynamics", supervisor: "Dr. Winter"> ]
|
||||||
|
|
||||||
|
In general, objects retrieved from the database are always delivered just as
|
||||||
|
they were created and saved, with the same type/class and fields. It doesn't
|
||||||
|
matter how you access these objects: be it through the model's own
|
||||||
|
managers/querysets, ForeignKey, ManyToMany or OneToOne fields.
|
||||||
|
|
||||||
|
The resulting querysets are polymorphic, and may deliver
|
||||||
objects of several different types in a single query result.
|
objects of several different types in a single query result.
|
||||||
|
|
||||||
Please see the concrete examples below as they demonstrate this best.
|
``django_polymorphic`` consists of just one add-on module, ``polymorphic.py``,
|
||||||
|
that adds this kind of automatic polymorphism to Django's model
|
||||||
|
inheritance system (for models that request this behaviour).
|
||||||
|
|
||||||
Please note that this module is very experimental code. See below for
|
Please see additional examples and the documentation here:
|
||||||
current restrictions, caveats, and performance implications.
|
|
||||||
|
|
||||||
|
http://bserve.webhop.org/wiki/django_polymorphic
|
||||||
|
|
||||||
Installation / Testing
|
or in the DOCS.rst file in this repository.
|
||||||
======================
|
|
||||||
|
|
||||||
Requirements
|
Status
|
||||||
------------
|
------
|
||||||
|
|
||||||
Django 1.1 and Python 2.5+. This code has been tested
|
|
||||||
on Django 1.1.1 with Python 2.5.4 and 2.6.4 on Linux.
|
|
||||||
|
|
||||||
Testing
|
|
||||||
-------
|
|
||||||
|
|
||||||
The repository (or tar file) contains a complete Django project
|
|
||||||
that may be used for testing and experimentation.
|
|
||||||
|
|
||||||
To run the included test suite, execute::
|
|
||||||
|
|
||||||
./manage test poly
|
|
||||||
|
|
||||||
'management/commands/polycmd.py' can be used for experiments
|
|
||||||
- modify this file to your liking, then run::
|
|
||||||
|
|
||||||
./manage syncdb # db is created in /tmp/... (settings.py)
|
|
||||||
./manage polycmd
|
|
||||||
|
|
||||||
Using polymorphic models in your own projects
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
Copy polymorphic.py (from the 'poly' dir) into a directory from where
|
|
||||||
you can import it, like your app directory (where your models.py and
|
|
||||||
views.py files live).
|
|
||||||
|
|
||||||
|
|
||||||
Defining Polymorphic Models
|
|
||||||
===========================
|
|
||||||
|
|
||||||
To make models polymorphic, use PolymorphicModel instead of Django's
|
|
||||||
models.Model as the superclass of your base model. All models
|
|
||||||
inheriting from your base class will be polymorphic as well::
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
Using Polymorphic Models
|
|
||||||
========================
|
|
||||||
|
|
||||||
Most of Django's standard ORM functionality is available
|
|
||||||
and works as expected:
|
|
||||||
|
|
||||||
Create some objects
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
>>> ModelA.objects.create(field1='A1')
|
|
||||||
>>> ModelB.objects.create(field1='B1', field2='B2')
|
|
||||||
>>> ModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
|
||||||
|
|
||||||
Query results are polymorphic
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
>>> ModelA.objects.all()
|
|
||||||
.
|
|
||||||
[ <ModelA: id 1, field1 (CharField)>,
|
|
||||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
|
||||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
|
||||||
|
|
||||||
Filtering for classes (equivalent to python's isinstance() ):
|
|
||||||
-------------------------------------------------------------
|
|
||||||
|
|
||||||
>>> 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 ...])
|
|
||||||
|
|
||||||
Polymorphic filtering (for fields in derived classes)
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
For example, cherrypicking objects from multiple derived classes
|
|
||||||
anywhere in the inheritance tree, using Q objects (with the
|
|
||||||
slightly enhanced 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)> ]
|
|
||||||
|
|
||||||
Combining Querysets of different types/models
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
Querysets may 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)> ]
|
|
||||||
|
|
||||||
Using Third Party Models (without modifying them)
|
|
||||||
-------------------------------------------------
|
|
||||||
|
|
||||||
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 MyThirdPartyModel(PolymorhpicModel, ThirdPartyModel):
|
|
||||||
pass # or add fields
|
|
||||||
|
|
||||||
Or instead integrating the third party model anywhere into an
|
|
||||||
existing polymorphic inheritance tree::
|
|
||||||
|
|
||||||
class MyModel(SomePolymorphicModel):
|
|
||||||
my_field = models.CharField(max_length=10)
|
|
||||||
|
|
||||||
class MyModelWithThirdParty(MyModel, ThirdPartyModel):
|
|
||||||
pass # or add fields
|
|
||||||
|
|
||||||
ManyToManyField, ForeignKey, OneToOneField
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
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)> ]
|
|
||||||
|
|
||||||
Non-Polymorphic Queries
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
>>> ModelA.base_objects.all()
|
|
||||||
.
|
|
||||||
[ <ModelA: id 1, field1 (CharField)>,
|
|
||||||
<ModelA: id 2, field1 (CharField)>,
|
|
||||||
<ModelA: id 3, field1 (CharField)> ]
|
|
||||||
|
|
||||||
Each polymorphic model has 'base_objects' defined as a normal
|
|
||||||
Django manager. Of course, arbitrary custom managers may be
|
|
||||||
added to the models as well.
|
|
||||||
|
|
||||||
|
|
||||||
Custom Managers, Querysets & Inheritance
|
|
||||||
========================================
|
|
||||||
|
|
||||||
Using a Custom Manager
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
For creating a custom polymorphic manager class, derive your manager
|
|
||||||
from PolymorphicManager instead of models.Manager. In your model
|
|
||||||
class, explicitly add the default manager first, and then your
|
|
||||||
custom manager::
|
|
||||||
|
|
||||||
class MyOrderedManager(PolymorphicManager):
|
|
||||||
def get_query_set(self):
|
|
||||||
return super(MyOrderedManager,self).get_query_set().order_by('some_field')
|
|
||||||
|
|
||||||
class MyModel(PolymorphicModel):
|
|
||||||
objects = PolymorphicManager() # add the default polymorphic manager first
|
|
||||||
ordered_objects = MyOrderedManager() # then add your own manager
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Manager Inheritance
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The current polymorphic models implementation unconditionally
|
|
||||||
inherits all managers from its base models (but only the
|
|
||||||
polymorphic base models).
|
|
||||||
|
|
||||||
An example (inheriting from MyModel above)::
|
|
||||||
|
|
||||||
class MyModel2(MyModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Managers inherited from MyModel, delivering MyModel2 objects (including MyModel2 subclass objects)
|
|
||||||
>>> MyModel2.objects.all()
|
|
||||||
>>> MyModel2.ordered_objects.all()
|
|
||||||
|
|
||||||
Manager inheritance is a somewhat complex topic that needs more
|
|
||||||
thought and more actual experience with real-world use-cases.
|
|
||||||
|
|
||||||
Using a Custom Queryset Class
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The PolymorphicManager class accepts one initialization argument,
|
|
||||||
which is the queryset class the manager should use. A custom
|
|
||||||
custom queryset class can be defined and used like this::
|
|
||||||
|
|
||||||
class MyQuerySet(PolymorphicQuerySet):
|
|
||||||
def my_queryset_method(...):
|
|
||||||
...
|
|
||||||
|
|
||||||
class MyModel(PolymorphicModel):
|
|
||||||
my_objects=PolymorphicManager(MyQuerySet)
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
Performance Considerations
|
|
||||||
==========================
|
|
||||||
|
|
||||||
The current implementation is pretty simple and does not use any
|
|
||||||
custom sql - it is purely based on the Django ORM. Right now 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 db query if all are
|
|
||||||
class ModelA. If 50 objects are ModelA and 50 are ModelB, then two
|
|
||||||
queries are executed. If result_objects contains only the base model
|
|
||||||
type (ModelA), the polymorphic models are just as efficient as plain
|
|
||||||
Django models (in terms of executed queries). The pathological worst
|
|
||||||
case is 101 db queries if result_objects contains 100 different
|
|
||||||
object types (with all of them subclasses of ModelA).
|
|
||||||
|
|
||||||
Performance ist relative: when Django users create their own
|
|
||||||
polymorphic ad-hoc solution (without a module like polymorphic.py),
|
|
||||||
they will tend to use a variation of ::
|
|
||||||
|
|
||||||
result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]
|
|
||||||
|
|
||||||
which of course has really bad performance. Relative to this, the
|
|
||||||
performance of the current polymorphic.py is rather good.
|
|
||||||
It's well possible that the current implementation is already
|
|
||||||
efficient enough for the majority of use cases.
|
|
||||||
|
|
||||||
Chunking: The implementation always requests objects in chunks of
|
|
||||||
size Polymorphic_QuerySet_objects_per_request. This limits the
|
|
||||||
complexity/duration for each query, including the pathological cases.
|
|
||||||
|
|
||||||
|
|
||||||
Possible Optimizations
|
|
||||||
======================
|
|
||||||
|
|
||||||
PolymorphicQuerySet can be optimized to require only one sql query
|
|
||||||
for the queryset evaluation and retrieval of all objects.
|
|
||||||
|
|
||||||
Basically, what ist needed is a possibility to pull in the fields
|
|
||||||
from all relevant sub-models with one sql query. In order to do this
|
|
||||||
on top of the Django ORM, some kind of enhhancement would be needed.
|
|
||||||
|
|
||||||
At first, it looks like a reverse select_related for OneToOne
|
|
||||||
relations might offer a solution (see http://code.djangoproject.com/ticket/7270)::
|
|
||||||
|
|
||||||
ModelA.objects.filter(...).select_related('modelb','modelb__modelc')
|
|
||||||
|
|
||||||
This approach has a number of problems, but nevertheless would
|
|
||||||
already execute the correct sql query and receive all the model
|
|
||||||
fields required from the db.
|
|
||||||
|
|
||||||
A kind of "select_related for values" might be a better solution::
|
|
||||||
|
|
||||||
ModelA.objects.filter(...).values_related(
|
|
||||||
[ base field name list ], {
|
|
||||||
'modelb' : [field name list ],
|
|
||||||
'modelb__modelc' : [ field name list ]
|
|
||||||
})
|
|
||||||
|
|
||||||
Django's lower level db API in QuerySet.query (see BaseQuery in
|
|
||||||
django.db.models.sql.query) might still allow other, better or easier
|
|
||||||
ways to implement the needed functionality.
|
|
||||||
|
|
||||||
SQL Complexity
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Regardless how these queries would be created, their complexity is
|
|
||||||
the same in any case:
|
|
||||||
|
|
||||||
With only one sql query, one sql join for each possible subclass
|
|
||||||
would be needed (BaseModel.__subclasses__(), recursively).
|
|
||||||
With two sql queries, the number of joins could be reduced to the
|
|
||||||
number of actuallly occurring subclasses in the result. A final
|
|
||||||
implementation might want to use one query only if the number of
|
|
||||||
possible subclasses (and therefore joins) is not too large, and
|
|
||||||
two queries otherwise (using the first query to determine the
|
|
||||||
actually occurring subclasses, reducing the number of joins for
|
|
||||||
the second).
|
|
||||||
|
|
||||||
A relatively large number of joins may be needed in both cases,
|
|
||||||
which raises concerns regarding the efficiency of these database
|
|
||||||
queries. It is currently unclear however, how many model classes
|
|
||||||
will actually be involved in typical use cases - the total number
|
|
||||||
of classes in the inheritance tree as well as the number of distinct
|
|
||||||
classes in query results. It may well turn out that the increased
|
|
||||||
number of joins is no problem for the DBMS in all realistic use
|
|
||||||
cases. Alternatively, if the sql query execution time is
|
|
||||||
significantly longer even in common use cases, this may still be
|
|
||||||
acceptable in exchange for the added functionality.
|
|
||||||
|
|
||||||
Let's not forget that all of the above is just about optimizations.
|
|
||||||
The current simplistic implementation already works well - perhaps
|
|
||||||
well enough for the majority of applications.
|
|
||||||
|
|
||||||
|
|
||||||
Restrictions, Caveats, Loose Ends
|
|
||||||
=================================
|
|
||||||
|
|
||||||
Unsupported Queryset Methods
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
+ aggregate() probably makes only sense in a purely non-OO/relational
|
|
||||||
way. So it seems an implementation would just fall back to the
|
|
||||||
Django vanilla equivalent.
|
|
||||||
|
|
||||||
+ annotate(): The current '_get_real_instances' would need minor
|
|
||||||
enhancement.
|
|
||||||
|
|
||||||
+ defer() and only(): Full support, including slight polymorphism
|
|
||||||
enhancements, seems to be straighforward
|
|
||||||
(depends on '_get_real_instances').
|
|
||||||
|
|
||||||
+ extra(): Does not really work with the current implementation of
|
|
||||||
'_get_real_instances'. It's unclear if it should be supported.
|
|
||||||
|
|
||||||
+ select_related(): This would probably need Django core support
|
|
||||||
for traversing the reverse model inheritance OneToOne relations
|
|
||||||
with Django's select_related(), e.g.:
|
|
||||||
*select_related('modela__modelb__foreignkeyfield')*.
|
|
||||||
Also needs more thought/investigation.
|
|
||||||
|
|
||||||
+ distinct() needs more thought and investigation as well
|
|
||||||
|
|
||||||
+ values() & values_list(): Implementation seems to be mostly
|
|
||||||
straighforward
|
|
||||||
|
|
||||||
|
|
||||||
Restrictions & Caveats
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
+ Diamond shaped inheritance: There seems to be a general problem
|
|
||||||
with diamond shaped multiple model inheritance with Django models
|
|
||||||
(tested with V1.1).
|
|
||||||
An example is here: http://code.djangoproject.com/ticket/10808.
|
|
||||||
This problem is aggravated when trying to enhance models.Model
|
|
||||||
by subclassing it instead of modifying Django core (as we do here
|
|
||||||
with PolymorphicModel).
|
|
||||||
|
|
||||||
+ The name and appname of the leaf model is stored in the base model
|
|
||||||
(the base model directly inheriting from PolymorphicModel).
|
|
||||||
If a model or an app is renamed, then these fields need to be
|
|
||||||
corrected too, if the db content should stay usable after the rename.
|
|
||||||
Aside from this, these two fields should probably be combined into
|
|
||||||
one field (more db/sql efficiency)
|
|
||||||
|
|
||||||
+ For all objects that are not instances of the base class type, but
|
|
||||||
instances of a subclass, the base class fields are currently
|
|
||||||
transferred twice from the database (an artefact of the current
|
|
||||||
implementation's simplicity).
|
|
||||||
|
|
||||||
+ __getattribute__ hack: For base model inheritance back relation
|
|
||||||
fields (like basemodel_ptr), as well as implicit model inheritance
|
|
||||||
forward relation fields, Django internally tries to use our
|
|
||||||
polymorphic manager/queryset in some places, which of course it
|
|
||||||
should not. Currently this is solved with hackish __getattribute__
|
|
||||||
in PolymorphicModel. A minor patch to Django core would probably
|
|
||||||
get rid of that.
|
|
||||||
|
|
||||||
+ "instance_of" and "not_instance_of" may need some optimization.
|
|
||||||
|
|
||||||
|
|
||||||
More Investigation Needed
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
There are a number of subtleties that have not yet been fully evaluated
|
|
||||||
or resolved, for example (among others) the exact implications of
|
|
||||||
'use_for_related_fields' in the polymorphic manager.
|
|
||||||
|
|
||||||
There may also well be larger issues of conceptual or technical nature
|
|
||||||
that might basically be showstoppers (but have not yet been found).
|
|
||||||
|
|
||||||
|
|
||||||
In General
|
|
||||||
----------
|
|
||||||
|
|
||||||
It is important to consider that this code is very experimental
|
|
||||||
and very insufficiently tested. A number of test cases are included
|
|
||||||
but they need to be expanded. This implementation is currently more
|
|
||||||
a tool for exploring the concept of polymorphism within the Django
|
|
||||||
framework. After careful testing and consideration it may perhaps be
|
|
||||||
useful for actual projects, but it might be too early for this
|
|
||||||
right now.
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
=====
|
|
||||||
|
|
||||||
- http://code.djangoproject.com/wiki/ModelInheritance
|
|
||||||
- http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html
|
|
||||||
- http://www.djangosnippets.org/snippets/1031/
|
|
||||||
- http://www.djangosnippets.org/snippets/1034/
|
|
||||||
- http://groups.google.com/group/django-developers/browse_frm/thread/7d40ad373ebfa912/a20fabc661b7035d?lnk=gst&q=model+inheritance+CORBA#a20fabc661b7035d
|
|
||||||
- http://groups.google.com/group/django-developers/browse_thread/thread/9bc2aaec0796f4e0/0b92971ffc0aa6f8?lnk=gst&q=inheritance#0b92971ffc0aa6f8
|
|
||||||
- http://groups.google.com/group/django-developers/browse_thread/thread/3947c594100c4adb/d8c0af3dacad412d?lnk=gst&q=inheritance#d8c0af3dacad412d
|
|
||||||
- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/b76c9d8c89a5574f
|
|
||||||
- http://peterbraden.co.uk/article/django-inheritance
|
|
||||||
- http://www.hopelessgeek.com/2009/11/25/a-hack-for-multi-table-inheritance-in-django
|
|
||||||
- http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982
|
|
||||||
- http://stackoverflow.com/questions/1581024/django-inheritance-how-to-have-one-method-for-all-subclasses
|
|
||||||
- http://groups.google.com/group/django-users/browse_thread/thread/cbdaf2273781ccab/e676a537d735d9ef?lnk=gst&q=polymorphic#e676a537d735d9ef
|
|
||||||
- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/bc18c18b2e83881e?lnk=gst&q=model+inheritance#bc18c18b2e83881e
|
|
||||||
- http://code.djangoproject.com/ticket/10808
|
|
||||||
- http://code.djangoproject.com/ticket/7270
|
|
||||||
|
|
||||||
|
|
||||||
Copyright
|
|
||||||
==========
|
|
||||||
|
|
||||||
| This code and affiliated files are (C) 2010 Bert Constantin and individual contributors.
|
|
||||||
| Please see LICENSE for more information.
|
|
||||||
|
|
||||||
|
This module is still very experimental. Please see the docs for current restrictions,
|
||||||
|
caveats, and performance implications.
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,20 @@ class Command(NoArgsCommand):
|
||||||
def handle_noargs(self, **options):
|
def handle_noargs(self, **options):
|
||||||
print "polycmd"
|
print "polycmd"
|
||||||
|
|
||||||
|
|
||||||
|
Project.objects.all().delete()
|
||||||
|
o=Project.objects.create(topic="John's gathering")
|
||||||
|
o=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
|
||||||
|
o=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
|
||||||
|
|
||||||
|
print Project.objects.all()
|
||||||
|
print
|
||||||
|
|
||||||
ModelA.objects.all().delete()
|
ModelA.objects.all().delete()
|
||||||
|
|
||||||
o=ModelA.objects.create(field1='A1')
|
o=ModelA.objects.create(field1='A1')
|
||||||
o=ModelB.objects.create(field1='B1', field2='B2')
|
o=ModelB.objects.create(field1='B1', field2='B2')
|
||||||
o=ModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
o=ModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
||||||
|
|
||||||
print ModelA.objects.all()
|
print ModelA.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,6 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet
|
from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet, ShowFields, ShowFieldsAndTypes
|
||||||
|
|
||||||
class ShowFieldContent(object):
|
|
||||||
def __repr__(self):
|
|
||||||
out = 'id %d, ' % (self.id); last = self._meta.fields[-1]
|
|
||||||
for f in self._meta.fields:
|
|
||||||
if f.name in [ 'id', 'p_classname', 'p_appname' ] or 'ptr' in f.name: continue
|
|
||||||
out += f.name + ' (' + type(f).__name__ + ')'
|
|
||||||
if isinstance(f, (models.ForeignKey)):
|
|
||||||
o = getattr(self, f.name)
|
|
||||||
out += ': "' + ('None' if o == None else o.__class__.__name__) + '"'
|
|
||||||
else:
|
|
||||||
out += ': "' + getattr(self, f.name) + '"'
|
|
||||||
if f != last: out += ', '
|
|
||||||
return '<' + self.__class__.__name__ + ': ' + out + '>'
|
|
||||||
|
|
||||||
class PlainA(models.Model):
|
class PlainA(models.Model):
|
||||||
field1 = models.CharField(max_length=10)
|
field1 = models.CharField(max_length=10)
|
||||||
|
|
@ -39,7 +25,7 @@ class ModelY(Base):
|
||||||
|
|
||||||
class Enhance_Plain(models.Model):
|
class Enhance_Plain(models.Model):
|
||||||
field_p = models.CharField(max_length=10)
|
field_p = models.CharField(max_length=10)
|
||||||
class Enhance_Base(ShowFieldContent, PolymorphicModel):
|
class Enhance_Base(ShowFieldsAndTypes, PolymorphicModel):
|
||||||
field_b = models.CharField(max_length=10)
|
field_b = models.CharField(max_length=10)
|
||||||
class Enhance_Inherit(Enhance_Base, Enhance_Plain):
|
class Enhance_Inherit(Enhance_Base, Enhance_Plain):
|
||||||
field_i = models.CharField(max_length=10)
|
field_i = models.CharField(max_length=10)
|
||||||
|
|
@ -54,7 +40,7 @@ class DiamondY(DiamondBase):
|
||||||
class DiamondXY(DiamondX, DiamondY):
|
class DiamondXY(DiamondX, DiamondY):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class RelationBase(ShowFieldContent, PolymorphicModel):
|
class RelationBase(ShowFieldsAndTypes, PolymorphicModel):
|
||||||
field_base = models.CharField(max_length=10)
|
field_base = models.CharField(max_length=10)
|
||||||
fk = models.ForeignKey('self', null=True)
|
fk = models.ForeignKey('self', null=True)
|
||||||
m2m = models.ManyToManyField('self')
|
m2m = models.ManyToManyField('self')
|
||||||
|
|
@ -71,7 +57,7 @@ class RelatingModel(models.Model):
|
||||||
class MyManager(PolymorphicManager):
|
class MyManager(PolymorphicManager):
|
||||||
def get_query_set(self):
|
def get_query_set(self):
|
||||||
return super(MyManager, self).get_query_set().order_by('-field1')
|
return super(MyManager, self).get_query_set().order_by('-field1')
|
||||||
class ModelWithMyManager(ShowFieldContent, ModelA):
|
class ModelWithMyManager(ShowFieldsAndTypes, ModelA):
|
||||||
objects = MyManager()
|
objects = MyManager()
|
||||||
field4 = models.CharField(max_length=10)
|
field4 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
@ -91,6 +77,13 @@ class MgrInheritA(models.Model):
|
||||||
class MgrInheritB(MgrInheritA):
|
class MgrInheritB(MgrInheritA):
|
||||||
mgrB = models.Manager()
|
mgrB = models.Manager()
|
||||||
field2 = models.CharField(max_length=10)
|
field2 = models.CharField(max_length=10)
|
||||||
class MgrInheritC(ShowFieldContent, MgrInheritB):
|
class MgrInheritC(ShowFieldsAndTypes, MgrInheritB):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class Project(ShowFields,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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,478 +1,15 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
===============================
|
|
||||||
Fully Polymorphic Django Models
|
Fully Polymorphic Django Models
|
||||||
===============================
|
|
||||||
|
|
||||||
|
Please see the examples and the documentation here:
|
||||||
|
|
||||||
Overview
|
http://bserve.webhop.org/wiki/django_polymorphic
|
||||||
========
|
|
||||||
|
|
||||||
'polymorphic.py' is an add-on module that adds fully automatic
|
or in the included README.rst and DOCS.rst files
|
||||||
polymorphism to the Django model inheritance system.
|
|
||||||
|
|
||||||
The effect is: For enabled models, objects retrieved from the
|
|
||||||
database are always delivered just as they were created and saved,
|
|
||||||
with the same type/class and fields - regardless how they are
|
|
||||||
retrieved. The resulting querysets are polymorphic, i.e. may deliver
|
|
||||||
objects of several different types in a single query result.
|
|
||||||
|
|
||||||
Please see the concrete examples below as they demonstrate this best.
|
|
||||||
|
|
||||||
Please note that this module is very experimental code. See below for
|
|
||||||
current restrictions, caveats, and performance implications.
|
|
||||||
|
|
||||||
|
|
||||||
Installation / Testing
|
|
||||||
======================
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
Django 1.1 and Python 2.5+. This code has been tested
|
|
||||||
on Django 1.1.1 with Python 2.5.4 and 2.6.4 on Linux.
|
|
||||||
|
|
||||||
Testing
|
|
||||||
-------
|
|
||||||
|
|
||||||
The repository (or tar file) contains a complete Django project
|
|
||||||
that may be used for testing and experimentation.
|
|
||||||
|
|
||||||
To run the included test suite, execute::
|
|
||||||
|
|
||||||
./manage test poly
|
|
||||||
|
|
||||||
'management/commands/polycmd.py' can be used for experiments
|
|
||||||
- modify this file to your liking, then run::
|
|
||||||
|
|
||||||
./manage syncdb # db is created in /tmp/... (settings.py)
|
|
||||||
./manage polycmd
|
|
||||||
|
|
||||||
Using polymorphic models in your own projects
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
Copy polymorphic.py (from the 'poly' dir) into a directory from where
|
|
||||||
you can import it, like your app directory (where your models.py and
|
|
||||||
views.py files live).
|
|
||||||
|
|
||||||
|
|
||||||
Defining Polymorphic Models
|
|
||||||
===========================
|
|
||||||
|
|
||||||
To make models polymorphic, use PolymorphicModel instead of Django's
|
|
||||||
models.Model as the superclass of your base model. All models
|
|
||||||
inheriting from your base class will be polymorphic as well::
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
Using Polymorphic Models
|
|
||||||
========================
|
|
||||||
|
|
||||||
Most of Django's standard ORM functionality is available
|
|
||||||
and works as expected:
|
|
||||||
|
|
||||||
Create some objects
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
>>> ModelA.objects.create(field1='A1')
|
|
||||||
>>> ModelB.objects.create(field1='B1', field2='B2')
|
|
||||||
>>> ModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
|
||||||
|
|
||||||
Query results are polymorphic
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
>>> ModelA.objects.all()
|
|
||||||
.
|
|
||||||
[ <ModelA: id 1, field1 (CharField)>,
|
|
||||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
|
||||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
|
||||||
|
|
||||||
Filtering for classes (equivalent to python's isinstance() ):
|
|
||||||
-------------------------------------------------------------
|
|
||||||
|
|
||||||
>>> 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 ...])
|
|
||||||
|
|
||||||
Polymorphic filtering (for fields in derived classes)
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
For example, cherrypicking objects from multiple derived classes
|
|
||||||
anywhere in the inheritance tree, using Q objects (with the
|
|
||||||
slightly enhanced 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)> ]
|
|
||||||
|
|
||||||
Combining Querysets of different types/models
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
Querysets may 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)> ]
|
|
||||||
|
|
||||||
Using Third Party Models (without modifying them)
|
|
||||||
-------------------------------------------------
|
|
||||||
|
|
||||||
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 MyThirdPartyModel(PolymorhpicModel, ThirdPartyModel):
|
|
||||||
pass # or add fields
|
|
||||||
|
|
||||||
Or instead integrating the third party model anywhere into an
|
|
||||||
existing polymorphic inheritance tree::
|
|
||||||
|
|
||||||
class MyModel(SomePolymorphicModel):
|
|
||||||
my_field = models.CharField(max_length=10)
|
|
||||||
|
|
||||||
class MyModelWithThirdParty(MyModel, ThirdPartyModel):
|
|
||||||
pass # or add fields
|
|
||||||
|
|
||||||
ManyToManyField, ForeignKey, OneToOneField
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
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)> ]
|
|
||||||
|
|
||||||
Non-Polymorphic Queries
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
>>> ModelA.base_objects.all()
|
|
||||||
.
|
|
||||||
[ <ModelA: id 1, field1 (CharField)>,
|
|
||||||
<ModelA: id 2, field1 (CharField)>,
|
|
||||||
<ModelA: id 3, field1 (CharField)> ]
|
|
||||||
|
|
||||||
Each polymorphic model has 'base_objects' defined as a normal
|
|
||||||
Django manager. Of course, arbitrary custom managers may be
|
|
||||||
added to the models as well.
|
|
||||||
|
|
||||||
|
|
||||||
Custom Managers, Querysets & Inheritance
|
|
||||||
========================================
|
|
||||||
|
|
||||||
Using a Custom Manager
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
For creating a custom polymorphic manager class, derive your manager
|
|
||||||
from PolymorphicManager instead of models.Manager. In your model
|
|
||||||
class, explicitly add the default manager first, and then your
|
|
||||||
custom manager::
|
|
||||||
|
|
||||||
class MyOrderedManager(PolymorphicManager):
|
|
||||||
def get_query_set(self):
|
|
||||||
return super(MyOrderedManager,self).get_query_set().order_by('some_field')
|
|
||||||
|
|
||||||
class MyModel(PolymorphicModel):
|
|
||||||
objects = PolymorphicManager() # add the default polymorphic manager first
|
|
||||||
ordered_objects = MyOrderedManager() # then add your own manager
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Manager Inheritance
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The current polymorphic models implementation unconditionally
|
|
||||||
inherits all managers from its base models (but only the
|
|
||||||
polymorphic base models).
|
|
||||||
|
|
||||||
An example (inheriting from MyModel above)::
|
|
||||||
|
|
||||||
class MyModel2(MyModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Managers inherited from MyModel, delivering MyModel2 objects (including MyModel2 subclass objects)
|
|
||||||
>>> MyModel2.objects.all()
|
|
||||||
>>> MyModel2.ordered_objects.all()
|
|
||||||
|
|
||||||
Manager inheritance is a somewhat complex topic that needs more
|
|
||||||
thought and more actual experience with real-world use-cases.
|
|
||||||
|
|
||||||
Using a Custom Queryset Class
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The PolymorphicManager class accepts one initialization argument,
|
|
||||||
which is the queryset class the manager should use. A custom
|
|
||||||
custom queryset class can be defined and used like this::
|
|
||||||
|
|
||||||
class MyQuerySet(PolymorphicQuerySet):
|
|
||||||
def my_queryset_method(...):
|
|
||||||
...
|
|
||||||
|
|
||||||
class MyModel(PolymorphicModel):
|
|
||||||
my_objects=PolymorphicManager(MyQuerySet)
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
Performance Considerations
|
|
||||||
==========================
|
|
||||||
|
|
||||||
The current implementation is pretty simple and does not use any
|
|
||||||
custom sql - it is purely based on the Django ORM. Right now 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 db query if all are
|
|
||||||
class ModelA. If 50 objects are ModelA and 50 are ModelB, then two
|
|
||||||
queries are executed. If result_objects contains only the base model
|
|
||||||
type (ModelA), the polymorphic models are just as efficient as plain
|
|
||||||
Django models (in terms of executed queries). The pathological worst
|
|
||||||
case is 101 db queries if result_objects contains 100 different
|
|
||||||
object types (with all of them subclasses of ModelA).
|
|
||||||
|
|
||||||
Performance ist relative: when Django users create their own
|
|
||||||
polymorphic ad-hoc solution (without a module like polymorphic.py),
|
|
||||||
they will tend to use a variation of ::
|
|
||||||
|
|
||||||
result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]
|
|
||||||
|
|
||||||
which of course has really bad performance. Relative to this, the
|
|
||||||
performance of the current polymorphic.py is rather good.
|
|
||||||
It's well possible that the current implementation is already
|
|
||||||
efficient enough for the majority of use cases.
|
|
||||||
|
|
||||||
Chunking: The implementation always requests objects in chunks of
|
|
||||||
size Polymorphic_QuerySet_objects_per_request. This limits the
|
|
||||||
complexity/duration for each query, including the pathological cases.
|
|
||||||
|
|
||||||
|
|
||||||
Possible Optimizations
|
|
||||||
======================
|
|
||||||
|
|
||||||
PolymorphicQuerySet can be optimized to require only one sql query
|
|
||||||
for the queryset evaluation and retrieval of all objects.
|
|
||||||
|
|
||||||
Basically, what ist needed is a possibility to pull in the fields
|
|
||||||
from all relevant sub-models with one sql query. In order to do this
|
|
||||||
on top of the Django ORM, some kind of enhhancement would be needed.
|
|
||||||
|
|
||||||
At first, it looks like a reverse select_related for OneToOne
|
|
||||||
relations might offer a solution (see http://code.djangoproject.com/ticket/7270)::
|
|
||||||
|
|
||||||
ModelA.objects.filter(...).select_related('modelb','modelb__modelc')
|
|
||||||
|
|
||||||
This approach has a number of problems, but nevertheless would
|
|
||||||
already execute the correct sql query and receive all the model
|
|
||||||
fields required from the db.
|
|
||||||
|
|
||||||
A kind of "select_related for values" might be a better solution::
|
|
||||||
|
|
||||||
ModelA.objects.filter(...).values_related(
|
|
||||||
[ base field name list ], {
|
|
||||||
'modelb' : [field name list ],
|
|
||||||
'modelb__modelc' : [ field name list ]
|
|
||||||
})
|
|
||||||
|
|
||||||
Django's lower level db API in QuerySet.query (see BaseQuery in
|
|
||||||
django.db.models.sql.query) might still allow other, better or easier
|
|
||||||
ways to implement the needed functionality.
|
|
||||||
|
|
||||||
SQL Complexity
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Regardless how these queries would be created, their complexity is
|
|
||||||
the same in any case:
|
|
||||||
|
|
||||||
With only one sql query, one sql join for each possible subclass
|
|
||||||
would be needed (BaseModel.__subclasses__(), recursively).
|
|
||||||
With two sql queries, the number of joins could be reduced to the
|
|
||||||
number of actuallly occurring subclasses in the result. A final
|
|
||||||
implementation might want to use one query only if the number of
|
|
||||||
possible subclasses (and therefore joins) is not too large, and
|
|
||||||
two queries otherwise (using the first query to determine the
|
|
||||||
actually occurring subclasses, reducing the number of joins for
|
|
||||||
the second).
|
|
||||||
|
|
||||||
A relatively large number of joins may be needed in both cases,
|
|
||||||
which raises concerns regarding the efficiency of these database
|
|
||||||
queries. It is currently unclear however, how many model classes
|
|
||||||
will actually be involved in typical use cases - the total number
|
|
||||||
of classes in the inheritance tree as well as the number of distinct
|
|
||||||
classes in query results. It may well turn out that the increased
|
|
||||||
number of joins is no problem for the DBMS in all realistic use
|
|
||||||
cases. Alternatively, if the sql query execution time is
|
|
||||||
significantly longer even in common use cases, this may still be
|
|
||||||
acceptable in exchange for the added functionality.
|
|
||||||
|
|
||||||
Let's not forget that all of the above is just about optimizations.
|
|
||||||
The current simplistic implementation already works well - perhaps
|
|
||||||
well enough for the majority of applications.
|
|
||||||
|
|
||||||
|
|
||||||
Restrictions, Caveats, Loose Ends
|
|
||||||
=================================
|
|
||||||
|
|
||||||
Unsupported Queryset Methods
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
+ aggregate() probably makes only sense in a purely non-OO/relational
|
|
||||||
way. So it seems an implementation would just fall back to the
|
|
||||||
Django vanilla equivalent.
|
|
||||||
|
|
||||||
+ annotate(): The current '_get_real_instances' would need minor
|
|
||||||
enhancement.
|
|
||||||
|
|
||||||
+ defer() and only(): Full support, including slight polymorphism
|
|
||||||
enhancements, seems to be straighforward
|
|
||||||
(depends on '_get_real_instances').
|
|
||||||
|
|
||||||
+ extra(): Does not really work with the current implementation of
|
|
||||||
'_get_real_instances'. It's unclear if it should be supported.
|
|
||||||
|
|
||||||
+ select_related(): This would probably need Django core support
|
|
||||||
for traversing the reverse model inheritance OneToOne relations
|
|
||||||
with Django's select_related(), e.g.:
|
|
||||||
*select_related('modela__modelb__foreignkeyfield')*.
|
|
||||||
Also needs more thought/investigation.
|
|
||||||
|
|
||||||
+ distinct() needs more thought and investigation as well
|
|
||||||
|
|
||||||
+ values() & values_list(): Implementation seems to be mostly
|
|
||||||
straighforward
|
|
||||||
|
|
||||||
|
|
||||||
Restrictions & Caveats
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
+ Diamond shaped inheritance: There seems to be a general problem
|
|
||||||
with diamond shaped multiple model inheritance with Django models
|
|
||||||
(tested with V1.1).
|
|
||||||
An example is here: http://code.djangoproject.com/ticket/10808.
|
|
||||||
This problem is aggravated when trying to enhance models.Model
|
|
||||||
by subclassing it instead of modifying Django core (as we do here
|
|
||||||
with PolymorphicModel).
|
|
||||||
|
|
||||||
+ The name and appname of the leaf model is stored in the base model
|
|
||||||
(the base model directly inheriting from PolymorphicModel).
|
|
||||||
If a model or an app is renamed, then these fields need to be
|
|
||||||
corrected too, if the db content should stay usable after the rename.
|
|
||||||
Aside from this, these two fields should probably be combined into
|
|
||||||
one field (more db/sql efficiency)
|
|
||||||
|
|
||||||
+ For all objects that are not instances of the base class type, but
|
|
||||||
instances of a subclass, the base class fields are currently
|
|
||||||
transferred twice from the database (an artefact of the current
|
|
||||||
implementation's simplicity).
|
|
||||||
|
|
||||||
+ __getattribute__ hack: For base model inheritance back relation
|
|
||||||
fields (like basemodel_ptr), as well as implicit model inheritance
|
|
||||||
forward relation fields, Django internally tries to use our
|
|
||||||
polymorphic manager/queryset in some places, which of course it
|
|
||||||
should not. Currently this is solved with hackish __getattribute__
|
|
||||||
in PolymorphicModel. A minor patch to Django core would probably
|
|
||||||
get rid of that.
|
|
||||||
|
|
||||||
+ "instance_of" and "not_instance_of" may need some optimization.
|
|
||||||
|
|
||||||
|
|
||||||
More Investigation Needed
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
There are a number of subtleties that have not yet been fully evaluated
|
|
||||||
or resolved, for example (among others) the exact implications of
|
|
||||||
'use_for_related_fields' in the polymorphic manager.
|
|
||||||
|
|
||||||
There may also well be larger issues of conceptual or technical nature
|
|
||||||
that might basically be showstoppers (but have not yet been found).
|
|
||||||
|
|
||||||
|
|
||||||
In General
|
|
||||||
----------
|
|
||||||
|
|
||||||
It is important to consider that this code is very experimental
|
|
||||||
and very insufficiently tested. A number of test cases are included
|
|
||||||
but they need to be expanded. This implementation is currently more
|
|
||||||
a tool for exploring the concept of polymorphism within the Django
|
|
||||||
framework. After careful testing and consideration it may perhaps be
|
|
||||||
useful for actual projects, but it might be too early for this
|
|
||||||
right now.
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
=====
|
|
||||||
|
|
||||||
- http://code.djangoproject.com/wiki/ModelInheritance
|
|
||||||
- http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html
|
|
||||||
- http://www.djangosnippets.org/snippets/1031/
|
|
||||||
- http://www.djangosnippets.org/snippets/1034/
|
|
||||||
- http://groups.google.com/group/django-developers/browse_frm/thread/7d40ad373ebfa912/a20fabc661b7035d?lnk=gst&q=model+inheritance+CORBA#a20fabc661b7035d
|
|
||||||
- http://groups.google.com/group/django-developers/browse_thread/thread/9bc2aaec0796f4e0/0b92971ffc0aa6f8?lnk=gst&q=inheritance#0b92971ffc0aa6f8
|
|
||||||
- http://groups.google.com/group/django-developers/browse_thread/thread/3947c594100c4adb/d8c0af3dacad412d?lnk=gst&q=inheritance#d8c0af3dacad412d
|
|
||||||
- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/b76c9d8c89a5574f
|
|
||||||
- http://peterbraden.co.uk/article/django-inheritance
|
|
||||||
- http://www.hopelessgeek.com/2009/11/25/a-hack-for-multi-table-inheritance-in-django
|
|
||||||
- http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982
|
|
||||||
- http://stackoverflow.com/questions/1581024/django-inheritance-how-to-have-one-method-for-all-subclasses
|
|
||||||
- http://groups.google.com/group/django-users/browse_thread/thread/cbdaf2273781ccab/e676a537d735d9ef?lnk=gst&q=polymorphic#e676a537d735d9ef
|
|
||||||
- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/bc18c18b2e83881e?lnk=gst&q=model+inheritance#bc18c18b2e83881e
|
|
||||||
- http://code.djangoproject.com/ticket/10808
|
|
||||||
- http://code.djangoproject.com/ticket/7270
|
|
||||||
|
|
||||||
|
|
||||||
Copyright
|
|
||||||
==========
|
|
||||||
|
|
||||||
| This code and affiliated files are (C) 2010 Bert Constantin and individual contributors.
|
|
||||||
| Please see LICENSE for more information.
|
|
||||||
|
|
||||||
|
Copyright: This code and affiliated files are (C) 2010 Bert Constantin
|
||||||
|
and individual contributors. Please see LICENSE for more information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
@ -497,7 +34,7 @@ class PolymorphicManager(models.Manager):
|
||||||
Usually not explicitly needed, except if a custom manager or
|
Usually not explicitly needed, except if a custom manager or
|
||||||
a custom queryset class is to be used.
|
a custom queryset class is to be used.
|
||||||
|
|
||||||
For more information, please see module docstring.
|
For more information, please see documentation.
|
||||||
"""
|
"""
|
||||||
use_for_related_fields = True
|
use_for_related_fields = True
|
||||||
|
|
||||||
|
|
@ -557,11 +94,10 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
base class query. The class of all of them is self.model (our base model).
|
base class query. The class of all of them is self.model (our base model).
|
||||||
|
|
||||||
Some, many or all of these objects were not created and stored as
|
Some, many or all of these objects were not created and stored as
|
||||||
class self.model, but as a class derived from self.model. So, these
|
class self.model, but as a class derived from self.model. We want to fetch
|
||||||
objects in base_result_objects have not the class they were created as
|
their missing fields and return them as they saved.
|
||||||
and do not contain all fields of their real class.
|
|
||||||
|
|
||||||
We identify them by looking at o.p_classname & o.p_appname, which specify
|
We identify these objects by looking at o.p_classname & o.p_appname, which specify
|
||||||
the real class of these objects (the class at the time they were saved).
|
the real class of these objects (the class at the time they were saved).
|
||||||
We replace them by the correct objects, which we fetch from the db.
|
We replace them by the correct objects, which we fetch from the db.
|
||||||
|
|
||||||
|
|
@ -958,7 +494,7 @@ class PolymorphicModel(models.Model):
|
||||||
Abstract base class that provides full polymorphism
|
Abstract base class that provides full polymorphism
|
||||||
to any model directly or indirectly derived from it
|
to any model directly or indirectly derived from it
|
||||||
|
|
||||||
For usage instructions & examples please see module docstring.
|
For usage instructions & examples please see documentation.
|
||||||
|
|
||||||
PolymorphicModel declares two fields for internal use (p_classname
|
PolymorphicModel declares two fields for internal use (p_classname
|
||||||
and p_appname) and provides a polymorphic manager as the
|
and p_appname) and provides a polymorphic manager as the
|
||||||
|
|
@ -990,8 +526,8 @@ class PolymorphicModel(models.Model):
|
||||||
|
|
||||||
def pre_save_polymorphic(self):
|
def pre_save_polymorphic(self):
|
||||||
"""
|
"""
|
||||||
Normally not needed - this function may be called manually in special use-cases.
|
Normally not needed.
|
||||||
|
This function may be called manually in special use-cases.
|
||||||
When the object is saved for the first time, we store its real class and app name
|
When the object is saved for the first time, we store its real class and app name
|
||||||
into p_classname and p_appname. When the object later is retrieved by
|
into p_classname and p_appname. When the object later is retrieved by
|
||||||
PolymorphicQuerySet, it uses these fields to figure out the real type of this object
|
PolymorphicQuerySet, it uses these fields to figure out the real type of this object
|
||||||
|
|
@ -1010,18 +546,18 @@ class PolymorphicModel(models.Model):
|
||||||
return super(PolymorphicModel, self).save(*args, **kwargs)
|
return super(PolymorphicModel, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def get_real_instance_class(self):
|
def get_real_instance_class(self):
|
||||||
"""Normally not needed - only if a non-polymorphic manager
|
"""Normally not needed.
|
||||||
(like base_objects) has been used to retrieve objects.
|
If a non-polymorphic manager (like base_objects) has been used to
|
||||||
Then these objects have the wrong class (the base class).
|
retrieve objects, then the real class/type of these objects may be
|
||||||
This function returns the real/correct class for this object."""
|
determined using this method."""
|
||||||
return models.get_model(self.p_appname, self.p_classname)
|
return models.get_model(self.p_appname, self.p_classname)
|
||||||
|
|
||||||
def get_real_instance(self):
|
def get_real_instance(self):
|
||||||
"""Normally not needed - only if a non-polymorphic manager
|
"""Normally not needed.
|
||||||
(like base_objects) has been used to retrieve objects.
|
If a non-polymorphic manager (like base_objects) has been used to
|
||||||
Then these objects have the wrong class (the base class).
|
retrieve objects, then the real class/type of these objects may be
|
||||||
This function returns the real object with the correct class
|
retrieve the complete object with i's real class/type and all fields.
|
||||||
and content. Each method call executes one db query."""
|
Each method call executes one db query."""
|
||||||
if self.p_classname == self.__class__.__name__ and self.p_appname == self.__class__._meta.app_label:
|
if self.p_classname == self.__class__.__name__ and self.p_appname == self.__class__._meta.app_label:
|
||||||
return self
|
return self
|
||||||
return self.get_real_instance_class().objects.get(id=self.id)
|
return self.get_real_instance_class().objects.get(id=self.id)
|
||||||
|
|
@ -1067,7 +603,7 @@ class PolymorphicModel(models.Model):
|
||||||
super(PolymorphicModel, self).__init__(*args, **kwargs)
|
super(PolymorphicModel, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"output object descriptions as seen in module docstring"
|
"output object descriptions as seen in documentation"
|
||||||
out = self.__class__.__name__ + ': id %d, ' % (self.id or - 1); last = self._meta.fields[-1]
|
out = self.__class__.__name__ + ': id %d, ' % (self.id or - 1); last = self._meta.fields[-1]
|
||||||
for f in self._meta.fields:
|
for f in self._meta.fields:
|
||||||
if f.name in [ 'id', 'p_classname', 'p_appname' ] or 'ptr' in f.name: continue
|
if f.name in [ 'id', 'p_classname', 'p_appname' ] or 'ptr' in f.name: continue
|
||||||
|
|
@ -1075,3 +611,36 @@ class PolymorphicModel(models.Model):
|
||||||
if f != last: out += ', '
|
if f != last: out += ', '
|
||||||
return '<' + out + '>'
|
return '<' + out + '>'
|
||||||
|
|
||||||
|
|
||||||
|
class ShowFields(object):
|
||||||
|
""" mixin that shows the object's class, it's fields and field contents """
|
||||||
|
def __repr__(self):
|
||||||
|
out = 'id %d, ' % (self.id); last = self._meta.fields[-1]
|
||||||
|
for f in self._meta.fields:
|
||||||
|
if f.name in [ 'id' ] + self.polymorphic_internal_model_fields or 'ptr' in f.name: continue
|
||||||
|
out += f.name
|
||||||
|
if isinstance(f, (models.ForeignKey)):
|
||||||
|
o = getattr(self, f.name)
|
||||||
|
out += ': "' + ('None' if o == None else o.__class__.__name__) + '"'
|
||||||
|
else:
|
||||||
|
out += ': "' + getattr(self, f.name) + '"'
|
||||||
|
if f != last: out += ', '
|
||||||
|
return '<' + (self.__class__.__name__ + ': ').ljust(17) + out + '>'
|
||||||
|
|
||||||
|
|
||||||
|
class ShowFieldsAndTypes(object):
|
||||||
|
""" like ShowFields, but also show field types """
|
||||||
|
def __repr__(self):
|
||||||
|
out = 'id %d, ' % (self.id); last = self._meta.fields[-1]
|
||||||
|
for f in self._meta.fields:
|
||||||
|
if f.name in [ 'id' ] + self.polymorphic_internal_model_fields or 'ptr' in f.name: continue
|
||||||
|
out += f.name + ' (' + type(f).__name__ + ')'
|
||||||
|
if isinstance(f, (models.ForeignKey)):
|
||||||
|
o = getattr(self, f.name)
|
||||||
|
out += ': "' + ('None' if o == None else o.__class__.__name__) + '"'
|
||||||
|
else:
|
||||||
|
out += ': "' + getattr(self, f.name) + '"'
|
||||||
|
if f != last: out += ', '
|
||||||
|
return '<' + self.__class__.__name__ + ': ' + out + '>'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue