updated docs

fix_request_path_info
Bert Constantin 2010-01-27 01:03:01 +01:00
parent c2b420aea0
commit e51425f5df
4 changed files with 115 additions and 109 deletions

17
.gitignore vendored
View File

@ -5,13 +5,18 @@
.project .project
.pydevproject .pydevproject
.settings .settings
pprep.py
mypoly.py
tmp
libraries-local
pushgit pushgit
pushhg pushhg
pushreg pushreg
mypoly.py
tmp ppreadme.py
poly2.py ppdocs.py
libraries-local README.html
README.html DOCS.html

126
DOCS.rst
View File

@ -1,22 +1,5 @@
=============================== .. contents:: Table of Contents
Fully Polymorphic Django Models :depth: 1
===============================
'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 Installation / Testing
====================== ======================
@ -54,8 +37,8 @@ views.py files live).
Defining Polymorphic Models Defining Polymorphic Models
=========================== ===========================
To make models polymorphic, use PolymorphicModel instead of Django's To make models polymorphic, use ``PolymorphicModel`` instead of Django's
models.Model as the superclass of your base model. All models ``models.Model`` as the superclass of your base model. All models
inheriting from your base class will be polymorphic as well:: inheriting from your base class will be polymorphic as well::
from polymorphic import PolymorphicModel from polymorphic import PolymorphicModel
@ -110,9 +93,9 @@ Polymorphic filtering (for fields in derived classes)
For example, cherrypicking objects from multiple derived classes For example, cherrypicking objects from multiple derived classes
anywhere in the inheritance tree, using Q objects (with the anywhere in the inheritance tree, using Q objects (with the
slightly enhanced syntax: exact model name + three _ + field name): syntax: ``exact model name + three _ + field name``):
>>> ModelA.objects.filter( Q( ModelB___field2 = 'B2' ) | Q( ModelC___field3 = 'C3' ) ) >>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') )
. .
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>, [ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ] <ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
@ -163,7 +146,7 @@ ManyToManyField, ForeignKey, OneToOneField
field1 = OneToOneField(ModelA) field1 = OneToOneField(ModelA)
then field1 may now also refer to objects of type ModelB or ModelC. then field1 may now also refer to objects of type ``ModelB`` or ``ModelC``.
A ManyToManyField example:: A ManyToManyField example::
@ -202,7 +185,7 @@ Using a Custom Manager
---------------------- ----------------------
For creating a custom polymorphic manager class, derive your manager For creating a custom polymorphic manager class, derive your manager
from PolymorphicManager instead of models.Manager. In your model from ``PolymorphicManager`` instead of ``models.Manager``. In your model
class, explicitly add the default manager first, and then your class, explicitly add the default manager first, and then your
custom manager:: custom manager::
@ -217,7 +200,7 @@ custom manager::
The first manager defined ('objects' in the example) is used by The first manager defined ('objects' in the example) is used by
Django as automatic manager for several purposes, including accessing Django as automatic manager for several purposes, including accessing
related objects. It must not filter objects and it's safest to use related objects. It must not filter objects and it's safest to use
the plain PolymorphicManager here. the plain ``PolymorphicManager`` here.
Manager Inheritance / Propagation Manager Inheritance / Propagation
--------------------------------- ---------------------------------
@ -230,7 +213,7 @@ An example (inheriting from MyModel above)::
class MyModel2(MyModel): class MyModel2(MyModel):
pass pass
# Managers inherited from MyModel, delivering MyModel2 objects (including MyModel2 subclass objects) # Managers inherited from MyModel, delivering MyModel2 (and subclass) objects
>>> MyModel2.objects.all() >>> MyModel2.objects.all()
>>> MyModel2.ordered_objects.all() >>> MyModel2.ordered_objects.all()
@ -242,7 +225,7 @@ managers are always fully propagated from all polymorphic base models
Using a Custom Queryset Class Using a Custom Queryset Class
----------------------------- -----------------------------
The PolymorphicManager class accepts one initialization argument, The ``PolymorphicManager`` class accepts one initialization argument,
which is the queryset class the manager should use. A custom which is the queryset class the manager should use. A custom
custom queryset class can be defined and used like this:: custom queryset class can be defined and used like this::
@ -259,51 +242,51 @@ Performance Considerations
========================== ==========================
The current implementation is pretty simple and does not use any The current implementation is pretty simple and does not use any
custom sql - it is purely based on the Django ORM. Right now the custom SQL - it is purely based on the Django ORM. Right now the
query :: query ::
result_objects = list( ModelA.objects.filter(...) ) result_objects = list( ModelA.objects.filter(...) )
performs one sql query to retrieve ModelA objects and one additional performs one SQL query to retrieve ``ModelA`` objects and one additional
query for each unique derived class occurring in result_objects. query for each unique derived class occurring in result_objects.
The best case for retrieving 100 objects is 1 db query if all are 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 class ``ModelA``. If 50 objects are ``ModelA`` and 50 are ModelB, then two
queries are executed. If result_objects contains only the base model queries are executed. If result_objects contains only the base model
type (ModelA), the polymorphic models are just as efficient as plain type (``ModelA``), the polymorphic models are just as efficient as plain
Django models (in terms of executed queries). The pathological worst Django models (in terms of executed queries). The pathological worst
case is 101 db queries if result_objects contains 100 different case is 101 db queries if result_objects contains 100 different
object types (with all of them subclasses of ModelA). object types (with all of them subclasses of ``ModelA``).
Performance ist relative: when Django users create their own Performance ist relative: when Django users create their own
polymorphic ad-hoc solution (without a module like polymorphic.py), polymorphic ad-hoc solution (without a module like ``polymorphic.py``),
this usually results in a variation of :: this usually results in a variation of ::
result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ] result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]
which of has really bad performance. Relative to this, the which of has really bad performance. Relative to this, the
performance of the current polymorphic.py is pretty good. performance of the current ``polymorphic.py`` is pretty good.
It may well be efficient enough for the majority of use cases. It may well be efficient enough for the majority of use cases.
Chunking: The implementation always requests objects in chunks of Chunking: The implementation always requests objects in chunks of
size Polymorphic_QuerySet_objects_per_request. This limits the size ``Polymorphic_QuerySet_objects_per_request``. This limits the
complexity/duration for each query, including the pathological cases. complexity/duration for each query, including the pathological cases.
Possible Optimizations Possible Optimizations
====================== ======================
PolymorphicQuerySet can be optimized to require only one SQL query ``PolymorphicQuerySet`` can be optimized to require only one SQL query
for the queryset evaluation and retrieval of all objects. for the queryset evaluation and retrieval of all objects.
Basically, what ist needed is a possibility to pull in the fields Basically, what ist needed is a possibility to pull in the fields
from all relevant sub-models with one sql query. However, some deeper from all relevant sub-models with one SQL query. However, some deeper
digging into the Django database layer will be required in order to digging into the Django database layer will be required in order to
make this happen. make this happen.
A viable option might be to get the sql query from the QuerySet A viable option might be to get the SQL query from the QuerySet
(probably from django.db.models.sql.compiler.SQLCompiler.as_sql), (probably from ``django.db.models.SQL.compiler.SQLCompiler.as_sql``),
making sure that all necessary joins are done, and then doing a making sure that all necessary joins are done, and then doing a
custom SQL request from there (like in SQLCompiler.execute_sql). custom SQL request from there (like in ``SQLCompiler.execute_sql``).
An optimized version could fall back to the current ORM-only An optimized version could fall back to the current ORM-only
implementation for all non-SQL databases. implementation for all non-SQL databases.
@ -311,9 +294,9 @@ implementation for all non-SQL databases.
SQL Complexity SQL Complexity
-------------- --------------
With only one sql query, one sql join for each possible subclass With only one SQL query, one SQL join for each possible subclass
would be needed (BaseModel.__subclasses__(), recursively). would be needed (``BaseModel.__subclasses__()``, recursively).
With two sql queries, the number of joins could be reduced to the With two SQL queries, the number of joins could be reduced to the
number of actuallly occurring subclasses in the result. A final number of actuallly occurring subclasses in the result. A final
implementation might want to use one query only if the number of implementation might want to use one query only if the number of
possible subclasses (and therefore joins) is not too large, and possible subclasses (and therefore joins) is not too large, and
@ -328,47 +311,55 @@ will actually be involved in typical use cases - the total number
of classes in the inheritance tree as well as the number of distinct 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 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 number of joins is no problem for the DBMS in all realistic use
cases. Alternatively, if the sql query execution time is cases. Alternatively, if the SQL query execution time is
significantly longer even in common use cases, this may still be significantly longer even in common use cases, this may still be
acceptable in exchange for the added functionality. acceptable in exchange for the added functionality.
Let's not forget that all of the above is just about optimizations. General
The current simplistic implementation already works well - perhaps -------------------
well enough for the majority of applications.
Let's not forget that all of the above is just about optimization.
The current implementation already works well - and perhaps well
enough for the majority of applications.
Also, it seems that further optimization (down to one DB request)
would be restricted to a small area of the code, straightforward
to implement, and mostly independent from the rest of the module.
So this optimization can be done at any later time (like when
it's needed).
Loose Ends Unsupported Methods, Restrictions & Caveats
========== ===========================================
Currently Unsupported Queryset Methods Currently Unsupported Queryset Methods
-------------------------------------- --------------------------------------
+ aggregate() probably makes only sense in a purely non-OO/relational + ``aggregate()`` probably makes only sense in a purely non-OO/relational
way. So it seems an implementation would just fall back to the way. So it seems an implementation would just fall back to the
Django vanilla equivalent. Django vanilla equivalent.
+ annotate(): The current '_get_real_instances' would need minor + ``annotate()``: The current '_get_real_instances' would need minor
enhancement. enhancement.
+ defer() and only(): Full support, including slight polymorphism + ``defer()`` and ``only()``: Full support, including slight polymorphism
enhancements, seems to be straighforward enhancements, seems to be straighforward
(depends on '_get_real_instances'). (depends on '_get_real_instances').
+ extra(): Does not really work with the current implementation of + ``extra()``: Does not really work with the current implementation of
'_get_real_instances'. It's unclear if it should be supported. '_get_real_instances'. It's unclear if it should be supported.
+ select_related(): This would probably need Django core support + ``select_related()``: This would probably need Django core support
for traversing the reverse model inheritance OneToOne relations for traversing the reverse model inheritance OneToOne relations
with Django's select_related(), e.g.: with Django's select_related(), e.g.:
*select_related('modela__modelb__foreignkeyfield')*. ``select_related('modela__modelb__foreignkeyfield')``.
Also needs more thought/investigation. Also needs more thought/investigation.
+ distinct() needs more thought and investigation as well + ``distinct()`` needs more thought and investigation as well
+ values() & values_list(): Implementation seems to be mostly + ``values()`` & ``values_list()``: Implementation seems to be mostly
straighforward straighforward
Restrictions & Caveats Restrictions & Caveats
---------------------- ----------------------
@ -380,12 +371,11 @@ Restrictions & Caveats
by subclassing it instead of modifying Django core (as we do here by subclassing it instead of modifying Django core (as we do here
with PolymorphicModel). with PolymorphicModel).
+ The name and appname of the leaf model is stored in the base model + A reference (``ContentType``) to the real/leaf model is stored
(the base model directly inheriting from PolymorphicModel). in the base model (the base model directly inheriting from
If a model or an app is renamed, then these fields need to be PolymorphicModel). If a model or an app is renamed, then Django's
corrected too, if the db content should stay usable after the rename. ContentType table needs to be corrected too, if the db content
Aside from this, these two fields should probably be combined into should stay usable after the rename.
one field (more db/sql efficiency)
+ For all objects that are not instances of the base class type, but + For all objects that are not instances of the base class type, but
instances of a subclass, the base class fields are currently instances of a subclass, the base class fields are currently
@ -396,9 +386,9 @@ Restrictions & Caveats
fields (like basemodel_ptr), as well as implicit model inheritance fields (like basemodel_ptr), as well as implicit model inheritance
forward relation fields, Django internally tries to use our forward relation fields, Django internally tries to use our
polymorphic manager/queryset in some places, which of course it polymorphic manager/queryset in some places, which of course it
should not. Currently this is solved with hackish __getattribute__ should not. Currently this is solved with a hacky __getattribute__
in PolymorphicModel. A minor patch to Django core would probably in PolymorphicModel, which causes some overhead. A minor patch t
get rid of that. Django core would probably get rid of that.
In General In General
---------- ----------

View File

@ -1,47 +1,55 @@
=============================== **2010-1-26**
Fully Polymorphic Django Models IMPORTANT - database schema change (more info in change log).
===============================
News
----
* 2010-1-26: IMPORTANT - database schema change (more info in change log).
I hope I got this change in early enough before anyone started to use I hope I got this change in early enough before anyone started to use
polymorphic.py in earnest. Sorry for any inconvenience. polymorphic.py in earnest. Sorry for any inconvenience.
This should be the final DB schema now! This should be the final DB schema now.
Usage, Examples, Installation & Documentation, Links
----------------------------------------------------
* Documentation_ and Overview_
* `Discussion, Questions, Suggestions`_
* GitHub_ - Bitbucket_ - `Download as TGZ`_ or ZIP_
.. _Documentation: http://bserve.webhop.org/wiki/django_polymorphic/doc
.. _Discussion, Questions, Suggestions: http://django-polymorphic.blogspot.com/2010/01/messages.html
.. _GitHub: http://github.com/bconstantin/django_polymorphic
.. _Bitbucket: http://bitbucket.org/bconstantin/django_polymorphic
.. _Download as TGZ: http://github.com/bconstantin/django_polymorphic/tarball/master
.. _ZIP: http://github.com/bconstantin/django_polymorphic/zipball/master
.. _Overview: http://bserve.webhop.org/wiki/django_polymorphic
What is django_polymorphic good for? What is django_polymorphic good for?
------------------------------------ ------------------------------------
If ``ArtProject`` and ``ResearchProject`` inherit from the model ``Project``:: If ``ArtProject`` and ``ResearchProject`` inherit from the model ``Project``:
>>> Project.objects.all() >>> Project.objects.all()
. .
[ <Project: id 1, topic: "John's Gathering">, [ <Project: id 1, topic: "John's Gathering">,
<ArtProject: id 2, topic: "Sculpting with Tim", artist: "T. Turner">, <ArtProject: id 2, topic: "Sculpting with Tim", artist: "T. Turner">,
<ResearchProject: id 3, topic: "Swallow Aerodynamics", supervisor: "Dr. Winter"> ] <ResearchProject: id 3, topic: "Swallow Aerodynamics", supervisor: "Dr. Winter"> ]
In general, objects retrieved from the database are always delivered just as In general, objects retrieved from the database are always returned back
they were created and saved, with the same type/class and fields. It doesn't with the same type/class and fields they were created and saved with.
matter how you access these objects: be it through the model's own It doesn't matter how these objects are retrieved: be it through the
managers/querysets, ForeignKey, ManyToMany or OneToOne fields. model's own managers/querysets, ForeignKeys, ManyToManyFields
or OneToOneFields.
The resulting querysets are polymorphic, and may deliver The resulting querysets are polymorphic, i.e they may deliver
objects of several different types in a single query result. objects of several different types in a single query result.
``django_polymorphic`` consists of just one add-on module, ``polymorphic.py``, ``django_polymorphic`` consists of just one add-on module, ``polymorphic.py``,
that adds this kind of automatic polymorphism to Django's model that adds this functionality to Django's model inheritance system
inheritance system (for models that request this behaviour). (for models that request this behaviour).
Please see additional examples and the documentation here:
http://bserve.webhop.org/wiki/django_polymorphic
or in the DOCS.rst file in this repository.
Status Status
------ ------
This module is still very experimental. Please see the docs for current restrictions, This module is still very experimental. Please see the docs for current restrictions,
caveats, and performance implications. caveats, and performance implications.

View File

@ -10,8 +10,7 @@ Please see the examples and documentation here:
or in the included README.rst and DOCS.rst files. or in the included README.rst and DOCS.rst files.
Copyright: Copyright:
This code and affiliated files are (C) by This code and affiliated files are (C) by Bert Constantin and individual contributors.
Bert Constantin and the individual contributors.
Please see LICENSE and AUTHORS for more information. Please see LICENSE and AUTHORS for more information.
""" """
@ -21,6 +20,7 @@ from django.db.models.query import QuerySet
from collections import defaultdict from collections import defaultdict
from pprint import pprint from pprint import pprint
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
import sys
# chunk-size: maximum number of objects requested per db-request # chunk-size: maximum number of objects requested per db-request
# by the polymorphic queryset.iterator() implementation # by the polymorphic queryset.iterator() implementation
@ -48,7 +48,7 @@ class PolymorphicManager(models.Manager):
return self.queryset_class(self.model) return self.queryset_class(self.model)
# Proxy all unknown method calls to the queryset, so that its members are # Proxy all unknown method calls to the queryset, so that its members are
# directly accessible from PolymorphicModel.objects. # directly accessible as PolymorphicModel.objects.*
# The advantage is that not yet known member functions of derived querysets will be proxied as well. # The advantage is that not yet known member functions of derived querysets will be proxied as well.
# We exclude any special functions (__) from this automatic proxying. # We exclude any special functions (__) from this automatic proxying.
def __getattr__(self, name): def __getattr__(self, name):
@ -109,8 +109,8 @@ class PolymorphicQuerySet(QuerySet):
ordered_id_list = [] # list of ids of result-objects in correct order ordered_id_list = [] # list of ids of result-objects in correct order
results = {} # polymorphic dict of result-objects, keyed with their id (no order) results = {} # polymorphic dict of result-objects, keyed with their id (no order)
# dict contains one entry for the different model types occurring in result, # dict contains one entry per unique model type occurring in result,
# in the format idlist_per_model['applabel.modelname']=[list-of-ids-for-this-model] # in the format idlist_per_model[modelclass]=[list-of-object-ids]
idlist_per_model = defaultdict(list) idlist_per_model = defaultdict(list)
# - sort base_result_object ids into idlist_per_model lists, depending on their real class; # - sort base_result_object ids into idlist_per_model lists, depending on their real class;
@ -307,7 +307,9 @@ def _translate_polymorphic_field_path(queryset_model, field_path):
if issubclass(model, models.Model) and model != models.Model: if issubclass(model, models.Model) and model != models.Model:
# model name is occurring twice in submodel inheritance tree => Error # model name is occurring twice in submodel inheritance tree => Error
if model.__name__ in result and model != result[model.__name__]: if model.__name__ in result and model != result[model.__name__]:
assert model, 'PolymorphicModel: model name is ambiguous: %s.%s, %s.%s!' % ( e = 'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s!\n'
e += 'In this case, please use the syntax: applabel__ModelName___field'
assert model, e % (
model._meta.app_label, model.__name__, model._meta.app_label, model.__name__,
result[model.__name__]._meta.app_label, result[model.__name__].__name__) result[model.__name__]._meta.app_label, result[model.__name__].__name__)
@ -451,6 +453,7 @@ class PolymorphicModelBase(ModelBase):
add_managers_keys.add(key) add_managers_keys.add(key)
return add_managers return add_managers
@classmethod @classmethod
def get_first_user_defined_manager(self, attrs): def get_first_user_defined_manager(self, attrs):
mgr_list = [] mgr_list = []
@ -617,7 +620,7 @@ class ShowFields(object):
else: else:
out += ': "' + getattr(self, f.name) + '"' out += ': "' + getattr(self, f.name) + '"'
if f != last: out += ', ' if f != last: out += ', '
return '<' + (self.__class__.__name__ + ': ').ljust(17) + out + '>' return '<' + (self.__class__.__name__ + ': ') + out + '>'
class ShowFieldsAndTypes(object): class ShowFieldsAndTypes(object):