updated docs, and moved them to DOCS.rst and README.rst

fix_request_path_info
Bert Constantin 2010-01-25 16:49:23 +01:00
parent e3346bd4fb
commit ba9a0b1302
7 changed files with 544 additions and 967 deletions

4
AUTHORS 100644
View File

@ -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.

434
DOCS.rst 100644
View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -20,10 +20,20 @@ class Command(NoArgsCommand):
def handle_noargs(self, **options): def handle_noargs(self, **options):
print "polycmd" print "polycmd"
ModelA.objects.all().delete()
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()
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()

View File

@ -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)

View File

@ -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 + '>'