initial commit
commit
576a16f2ab
|
|
@ -0,0 +1,14 @@
|
||||||
|
.hg
|
||||||
|
*.svn
|
||||||
|
*.pyc
|
||||||
|
*~
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
.settings
|
||||||
|
pprep.py
|
||||||
|
pushgit
|
||||||
|
pushhg
|
||||||
|
pushreg
|
||||||
|
mypoly.py
|
||||||
|
tmp
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright (c) 2009 by Bert Constantin and individual contributors.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* The names of the contributors may not be used to endorse or
|
||||||
|
promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -0,0 +1,471 @@
|
||||||
|
|
||||||
|
===============================
|
||||||
|
Fully Polymorphic Django Models
|
||||||
|
===============================
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
'polymorphic.py' is an add-on module that adds fully 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 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 any
|
||||||
|
restrictions by simply 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 the base models. An example::
|
||||||
|
|
||||||
|
class MyModel2(MyModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Managers inherited from MyModel
|
||||||
|
>>> 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.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from django.core.management import execute_manager
|
||||||
|
try:
|
||||||
|
import settings # Assumed to be in the same directory.
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
execute_manager(settings)
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This module is a scratchpad for general development, testing & debugging
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.core.management.base import NoArgsCommand
|
||||||
|
from django.db.models import connection
|
||||||
|
from poly.models import *
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
def reset_queries():
|
||||||
|
connection.queries=[]
|
||||||
|
|
||||||
|
def show_queries():
|
||||||
|
print; print 'QUERIES:',len(connection.queries); pprint(connection.queries); print; connection.queries=[]
|
||||||
|
|
||||||
|
class Command(NoArgsCommand):
|
||||||
|
help = ""
|
||||||
|
|
||||||
|
def handle_noargs(self, **options):
|
||||||
|
print "polycmd"
|
||||||
|
|
||||||
|
ModelA.objects.all().delete()
|
||||||
|
|
||||||
|
o=ModelA.objects.create(field1='A1')
|
||||||
|
o=ModelB.objects.create(field1='B1', field2='B2')
|
||||||
|
o=ModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
||||||
|
|
||||||
|
print ModelA.objects.all()
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet
|
||||||
|
|
||||||
|
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):
|
||||||
|
field1 = models.CharField(max_length=10)
|
||||||
|
class PlainB(PlainA):
|
||||||
|
field2 = models.CharField(max_length=10)
|
||||||
|
class PlainC(PlainB):
|
||||||
|
field3 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
class Base(PolymorphicModel):
|
||||||
|
field_b = models.CharField(max_length=10)
|
||||||
|
class ModelX(Base):
|
||||||
|
field_x = models.CharField(max_length=10)
|
||||||
|
class ModelY(Base):
|
||||||
|
field_y = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
class Enhance_Plain(models.Model):
|
||||||
|
field_p = models.CharField(max_length=10)
|
||||||
|
class Enhance_Base(ShowFieldContent, PolymorphicModel):
|
||||||
|
field_b = models.CharField(max_length=10)
|
||||||
|
class Enhance_Inherit(Enhance_Base, Enhance_Plain):
|
||||||
|
field_i = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
|
||||||
|
class DiamondBase(models.Model):
|
||||||
|
field_b = models.CharField(max_length=10)
|
||||||
|
class DiamondX(DiamondBase):
|
||||||
|
field_x = models.CharField(max_length=10)
|
||||||
|
class DiamondY(DiamondBase):
|
||||||
|
field_y = models.CharField(max_length=10)
|
||||||
|
class DiamondXY(DiamondX, DiamondY):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RelationBase(ShowFieldContent, PolymorphicModel):
|
||||||
|
field_base = models.CharField(max_length=10)
|
||||||
|
fk = models.ForeignKey('self', null=True)
|
||||||
|
m2m = models.ManyToManyField('self')
|
||||||
|
class RelationA(RelationBase):
|
||||||
|
field_a = models.CharField(max_length=10)
|
||||||
|
class RelationB(RelationBase):
|
||||||
|
field_b = models.CharField(max_length=10)
|
||||||
|
class RelationBC(RelationB):
|
||||||
|
field_c = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
class RelatingModel(models.Model):
|
||||||
|
many2many = models.ManyToManyField(ModelA)
|
||||||
|
|
||||||
|
class MyManager(PolymorphicManager):
|
||||||
|
def get_query_set(self):
|
||||||
|
return super(MyManager, self).get_query_set().order_by('-field1')
|
||||||
|
class ModelWithMyManager(ShowFieldContent, ModelA):
|
||||||
|
objects = MyManager()
|
||||||
|
field4 = models.CharField(max_length=10)
|
||||||
|
|
||||||
|
class MROBase1(PolymorphicModel):
|
||||||
|
objects = MyManager()
|
||||||
|
class MROBase2(MROBase1):
|
||||||
|
pass # Django vanilla inheritance does not inherit MyManager as _default_manager here
|
||||||
|
class MROBase3(models.Model):
|
||||||
|
objects = PolymorphicManager()
|
||||||
|
class MRODerived(MROBase2, MROBase3):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MgrInheritA(models.Model):
|
||||||
|
mgrA = models.Manager()
|
||||||
|
mgrA2 = models.Manager()
|
||||||
|
field1 = models.CharField(max_length=10)
|
||||||
|
class MgrInheritB(MgrInheritA):
|
||||||
|
mgrB = models.Manager()
|
||||||
|
field2 = models.CharField(max_length=10)
|
||||||
|
class MgrInheritC(ShowFieldContent, MgrInheritB):
|
||||||
|
pass
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,148 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#disabletests = 1
|
||||||
|
"""
|
||||||
|
#######################################################
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
>>> settings.DEBUG=True
|
||||||
|
|
||||||
|
### simple inheritance
|
||||||
|
|
||||||
|
>>> o=ModelA.objects.create(field1='A1')
|
||||||
|
>>> o=ModelB.objects.create(field1='B1', field2='B2')
|
||||||
|
>>> o=ModelC.objects.create(field1='C1', field2='C2', field3='C3')
|
||||||
|
|
||||||
|
>>> ModelA.objects.all()
|
||||||
|
[ <ModelA: id 1, field1 (CharField)>,
|
||||||
|
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||||
|
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||||
|
|
||||||
|
### class filtering, instance_of, not_instance_of
|
||||||
|
|
||||||
|
>>> ModelA.objects.instance_of(ModelB)
|
||||||
|
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||||
|
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||||
|
|
||||||
|
>>> ModelA.objects.not_instance_of(ModelB)
|
||||||
|
[ <ModelA: id 1, field1 (CharField)> ]
|
||||||
|
|
||||||
|
### polymorphic filtering
|
||||||
|
|
||||||
|
>>> 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)> ]
|
||||||
|
|
||||||
|
### get & delete
|
||||||
|
|
||||||
|
>>> oa=ModelA.objects.get(id=2)
|
||||||
|
>>> oa
|
||||||
|
<ModelB: id 2, field1 (CharField), field2 (CharField)>
|
||||||
|
|
||||||
|
>>> oa.delete()
|
||||||
|
>>> ModelA.objects.all()
|
||||||
|
[ <ModelA: id 1, field1 (CharField)>,
|
||||||
|
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||||
|
|
||||||
|
### queryset combining
|
||||||
|
|
||||||
|
>>> o=ModelX.objects.create(field_x='x')
|
||||||
|
>>> o=ModelY.objects.create(field_y='y')
|
||||||
|
|
||||||
|
>>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)
|
||||||
|
[ <ModelX: id 1, field_b (CharField), field_x (CharField)>,
|
||||||
|
<ModelY: id 2, field_b (CharField), field_y (CharField)> ]
|
||||||
|
|
||||||
|
### multiple inheritance, subclassing third party models (mix PolymorphicModel with models.Model)
|
||||||
|
|
||||||
|
>>> o = Enhance_Base.objects.create(field_b='b-base')
|
||||||
|
>>> o = Enhance_Inherit.objects.create(field_b='b-inherit', field_p='p', field_i='i')
|
||||||
|
|
||||||
|
>>> Enhance_Base.objects.all()
|
||||||
|
[ <Enhance_Base: id 1, field_b (CharField): "b-base">,
|
||||||
|
<Enhance_Inherit: id 2, field_b (CharField): "b-inherit", field_p (CharField): "p", field_i (CharField): "i"> ]
|
||||||
|
|
||||||
|
### ForeignKey, ManyToManyField
|
||||||
|
|
||||||
|
>>> obase=RelationBase.objects.create(field_base='base')
|
||||||
|
>>> oa=RelationA.objects.create(field_base='A1', field_a='A2', fk=obase)
|
||||||
|
>>> ob=RelationB.objects.create(field_base='B1', field_b='B2', fk=oa)
|
||||||
|
>>> oc=RelationBC.objects.create(field_base='C1', field_b='C2', field_c='C3', fk=oa)
|
||||||
|
>>> oa.m2m.add(oa); oa.m2m.add(ob)
|
||||||
|
|
||||||
|
>>> RelationBase.objects.all()
|
||||||
|
[ <RelationBase: id 1, field_base (CharField): "base", fk (ForeignKey): "None">,
|
||||||
|
<RelationA: id 2, field_base (CharField): "A1", fk (ForeignKey): "RelationBase", field_a (CharField): "A2">,
|
||||||
|
<RelationB: id 3, field_base (CharField): "B1", fk (ForeignKey): "RelationA", field_b (CharField): "B2">,
|
||||||
|
<RelationBC: id 4, field_base (CharField): "C1", fk (ForeignKey): "RelationA", field_b (CharField): "C2", field_c (CharField): "C3"> ]
|
||||||
|
|
||||||
|
>>> oa=RelationBase.objects.get(id=2)
|
||||||
|
>>> oa.fk
|
||||||
|
<RelationBase: id 1, field_base (CharField): "base", fk (ForeignKey): "None">
|
||||||
|
|
||||||
|
>>> oa.relationbase_set.all()
|
||||||
|
[ <RelationB: id 3, field_base (CharField): "B1", fk (ForeignKey): "RelationA", field_b (CharField): "B2">,
|
||||||
|
<RelationBC: id 4, field_base (CharField): "C1", fk (ForeignKey): "RelationA", field_b (CharField): "C2", field_c (CharField): "C3"> ]
|
||||||
|
|
||||||
|
>>> ob=RelationBase.objects.get(id=3)
|
||||||
|
>>> ob.fk
|
||||||
|
<RelationA: id 2, field_base (CharField): "A1", fk (ForeignKey): "RelationBase", field_a (CharField): "A2">
|
||||||
|
|
||||||
|
>>> oa=RelationA.objects.get()
|
||||||
|
>>> oa.m2m.all()
|
||||||
|
[ <RelationA: id 2, field_base (CharField): "A1", fk (ForeignKey): "RelationBase", field_a (CharField): "A2">,
|
||||||
|
<RelationB: id 3, field_base (CharField): "B1", fk (ForeignKey): "RelationA", field_b (CharField): "B2"> ]
|
||||||
|
|
||||||
|
### user-defined manager
|
||||||
|
|
||||||
|
>>> o=ModelWithMyManager.objects.create(field1='D1a', field4='D4a')
|
||||||
|
>>> o=ModelWithMyManager.objects.create(field1='D1b', field4='D4b')
|
||||||
|
|
||||||
|
>>> ModelWithMyManager.objects.all()
|
||||||
|
[ <ModelWithMyManager: id 5, field1 (CharField): "D1b", field4 (CharField): "D4b">,
|
||||||
|
<ModelWithMyManager: id 4, field1 (CharField): "D1a", field4 (CharField): "D4a"> ]
|
||||||
|
|
||||||
|
>>> type(ModelWithMyManager.objects)
|
||||||
|
<class 'poly.models.MyManager'>
|
||||||
|
>>> type(ModelWithMyManager._default_manager)
|
||||||
|
<class 'poly.models.MyManager'>
|
||||||
|
|
||||||
|
### Manager Inheritance
|
||||||
|
|
||||||
|
>>> type(MRODerived.objects) # MRO
|
||||||
|
<class 'poly.models.MyManager'>
|
||||||
|
|
||||||
|
# check for correct default manager
|
||||||
|
>>> type(MROBase1._default_manager)
|
||||||
|
<class 'poly.models.MyManager'>
|
||||||
|
|
||||||
|
# Django vanilla inheritance does not inherit MyManager as _default_manager here
|
||||||
|
>>> type(MROBase2._default_manager)
|
||||||
|
<class 'poly.models.MyManager'>
|
||||||
|
|
||||||
|
### Django model inheritance diamond problem, fails for Django 1.1
|
||||||
|
|
||||||
|
#>>> o=DiamondXY.objects.create(field_b='b', field_x='x', field_y='y')
|
||||||
|
#>>> print 'DiamondXY fields 1: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||||
|
#DiamondXY fields 1: field_b "a", field_x "x", field_y "y"
|
||||||
|
|
||||||
|
>>> settings.DEBUG=False
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import settings
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from models import *
|
||||||
|
|
||||||
|
class testclass(TestCase):
|
||||||
|
def test_diamond_inheritance(self):
|
||||||
|
# Django diamond problem
|
||||||
|
o = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y')
|
||||||
|
print 'DiamondXY fields 1: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||||
|
o = DiamondXY.objects.get()
|
||||||
|
print 'DiamondXY fields 2: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||||
|
if o.field_b != 'b': print '# Django model inheritance diamond problem detected'
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Django settings for polymorphic_demo project.
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
|
ADMINS = (
|
||||||
|
# ('Your Name', 'your_email@domain.com'),
|
||||||
|
)
|
||||||
|
|
||||||
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
|
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||||
|
DATABASE_NAME = '/tmp/django-polymorphic-test-db.sqlite3' # Or path to database file if using sqlite3.
|
||||||
|
DATABASE_USER = '' # Not used with sqlite3.
|
||||||
|
DATABASE_PASSWORD = '' # Not used with sqlite3.
|
||||||
|
|
||||||
|
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
|
||||||
|
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||||
|
|
||||||
|
# Local time zone for this installation. Choices can be found here:
|
||||||
|
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||||
|
# although not all choices may be available on all operating systems.
|
||||||
|
# If running in a Windows environment this must be set to the same as your
|
||||||
|
# system time zone.
|
||||||
|
TIME_ZONE = 'America/Chicago'
|
||||||
|
|
||||||
|
# Language code for this installation. All choices can be found here:
|
||||||
|
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
# If you set this to False, Django will make some optimizations so as not
|
||||||
|
# to load the internationalization machinery.
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
# Absolute path to the directory that holds media.
|
||||||
|
# Example: "/home/media/media.lawrence.com/"
|
||||||
|
MEDIA_ROOT = ''
|
||||||
|
|
||||||
|
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||||
|
# trailing slash if there is a path component (optional in other cases).
|
||||||
|
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||||
|
MEDIA_URL = ''
|
||||||
|
|
||||||
|
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||||
|
# trailing slash.
|
||||||
|
# Examples: "http://foo.com/media/", "/media/".
|
||||||
|
ADMIN_MEDIA_PREFIX = '/media/'
|
||||||
|
|
||||||
|
# Make this unique, and don't share it with anybody.
|
||||||
|
SECRET_KEY = 'nk=c&k+c&#+)8557)%&0auysdd3g^sfq6@rw8_x1k8)-p@y)!('
|
||||||
|
|
||||||
|
# List of callables that know how to import templates from various sources.
|
||||||
|
TEMPLATE_LOADERS = (
|
||||||
|
'django.template.loaders.filesystem.load_template_source',
|
||||||
|
'django.template.loaders.app_directories.load_template_source',
|
||||||
|
# 'django.template.loaders.eggs.load_template_source',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_URLCONF = ''
|
||||||
|
|
||||||
|
TEMPLATE_DIRS = (
|
||||||
|
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
||||||
|
# Always use forward slashes, even on Windows.
|
||||||
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
|
)
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
#'django.contrib.auth',
|
||||||
|
#'django.contrib.contenttypes',
|
||||||
|
#'django.contrib.sessions',
|
||||||
|
#'django.contrib.sites',
|
||||||
|
'poly', # this Django app is for testing and experimentation; not needed otherwise
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue