doc- and minor code updates
parent
c7ac78e08d
commit
2a740e9b10
97
DOCS.rst
97
DOCS.rst
|
|
@ -9,8 +9,6 @@ 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.
|
||||
Django's ContentType framework is used (part of Django).
|
||||
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
|
@ -32,16 +30,14 @@ to your liking, then run::
|
|||
Using polymorphic models in your own projects
|
||||
---------------------------------------------
|
||||
|
||||
The best way for now is probably to just copy the ``polymorphic`` directory
|
||||
into your project dir. If you want to use the management command
|
||||
``polymorphic_dumpdata``, then you also need to add ``polymorphic``
|
||||
to your INSTALLED_APPS setting. The ContentType framework
|
||||
(``django.contrib.contenttypes``) needs to be listed in INSTALLED_APPS
|
||||
as well (usually it already is).
|
||||
The easiest way for now is to just copy the ``polymorphic`` directory
|
||||
(under ``django_polymorphic``) into your Django project dir.
|
||||
|
||||
It's also still possible to use ``polymorphic.py`` only, as a single file
|
||||
add-on module, copied to somewhere where it can be imported (like your
|
||||
own app dir).
|
||||
If you want to use the management command ``polymorphic_dumpdata``, then
|
||||
you need to add ``polymorphic`` to your INSTALLED_APPS setting as well.
|
||||
|
||||
In any case, the ContentType framework (``django.contrib.contenttypes``)
|
||||
needs to be listed in INSTALLED_APPS (usually it already is).
|
||||
|
||||
|
||||
Defining Polymorphic Models
|
||||
|
|
@ -193,8 +189,14 @@ manage.py dumpdata
|
|||
Django's standard ``dumpdata`` command requires non-polymorphic
|
||||
behaviour from the querysets it uses and produces incomplete
|
||||
results with polymorphic models. Django_polymorphic includes
|
||||
a slightly modified version, named ``polymorphic_dumpdata``.
|
||||
Just use this command instead of Django's (see "installation/testing").
|
||||
a slightly modified version, named ``polymorphic_dumpdata``
|
||||
that fixes this. Just use this command instead of Django's
|
||||
(see "installation/testing").
|
||||
|
||||
Please note that there are problems using ContentType together
|
||||
with Django's seralisation or fixtures (and all polymorphic models
|
||||
use ContentType). This issue seems to be resolved with Django 1.2
|
||||
(changeset 11863): http://code.djangoproject.com/ticket/7052
|
||||
|
||||
|
||||
Custom Managers, Querysets & Inheritance
|
||||
|
|
@ -221,12 +223,12 @@ 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
|
||||
---------------------------------
|
||||
Manager Inheritance
|
||||
-------------------
|
||||
|
||||
Polymorphic models unconditionally propagate/inherit all managers from
|
||||
their base models, as long as these are polymorphic. This means that all
|
||||
managers inherited from polymorphic base models work just the same as if
|
||||
Polymorphic models inherit/propagate all managers from their
|
||||
base models, as long as these are polymorphic. This means that all
|
||||
managers defined in polymorphic base models work just the same as if
|
||||
they were defined in the new model.
|
||||
|
||||
An example (inheriting from MyModel above)::
|
||||
|
|
@ -332,18 +334,18 @@ 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.
|
||||
|
||||
General
|
||||
-------------------
|
||||
In General
|
||||
----------
|
||||
|
||||
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).
|
||||
would be restricted to a relatively small area of the code, and
|
||||
be mostly independent from the rest of the module.
|
||||
So it seems this optimization can be done at any later time
|
||||
(like when it's needed).
|
||||
|
||||
|
||||
Unsupported Methods, Restrictions & Caveats
|
||||
|
|
@ -360,26 +362,31 @@ Currently Unsupported Queryset Methods
|
|||
enhancement.
|
||||
|
||||
+ ``defer()`` and ``only()``: Full support, including slight polymorphism
|
||||
enhancements, seems to be straighforward
|
||||
(depends on '_get_real_instances').
|
||||
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.
|
||||
+ ``select_related()`` works just as usual, but it can not (yet) be used
|
||||
to select relations in derived models
|
||||
(like ``ModelA.objects.select_related('ModelC___fieldxy')`` )
|
||||
|
||||
+ ``distinct()`` needs more thought and investigation as well
|
||||
+ ``distinct()`` needs more thought and investigation
|
||||
|
||||
+ ``values()`` & ``values_list()``: Implementation seems to be mostly straighforward
|
||||
|
||||
+ ``values()`` & ``values_list()``: Implementation seems to be mostly
|
||||
straighforward
|
||||
|
||||
Restrictions & Caveats
|
||||
----------------------
|
||||
|
||||
* ``Django 1.1 only``: When ContentType is used in models, Django's
|
||||
seralisation or fixtures cannot be used. This issue seems to be
|
||||
resolved for Django 1.2 (changeset 11863: Fixed #7052,
|
||||
Added support for natural keys in serialization).
|
||||
|
||||
+ http://code.djangoproject.com/ticket/7052
|
||||
+ http://stackoverflow.com/questions/853796/problems-with-contenttypes-when-loading-a-fixture-in-django
|
||||
|
||||
* Diamond shaped inheritance: There seems to be a general problem
|
||||
with diamond shaped multiple model inheritance with Django models
|
||||
(tested with V1.1).
|
||||
|
|
@ -394,13 +401,6 @@ Restrictions & Caveats
|
|||
ContentType table needs to be corrected too, if the db content
|
||||
should stay usable after the rename.
|
||||
|
||||
* The use of ContentType together with Django's seralisation or
|
||||
fixtures seems to pose problems up to Django 1.1. This issue
|
||||
seems to be resolved for Django 1.2 (changeset 11863: Fixed #7052,
|
||||
Added support for natural keys in serialization).
|
||||
http://code.djangoproject.com/ticket/7052
|
||||
http://stackoverflow.com/questions/853796/problems-with-contenttypes-when-loading-a-fixture-in-django
|
||||
|
||||
* For all objects that are not instances of the base class, but
|
||||
instances of a subclass, the base class fields are currently
|
||||
transferred twice from the database (an artefact of the current
|
||||
|
|
@ -411,19 +411,20 @@ Restrictions & Caveats
|
|||
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 a hacky __getattribute__
|
||||
in PolymorphicModel, which causes some overhead. A minor patch t
|
||||
in PolymorphicModel, which causes some overhead. A minor patch to
|
||||
Django core would probably get rid of that.
|
||||
|
||||
In General
|
||||
----------
|
||||
|
||||
It's important to consider that this code is still very new and experimental.
|
||||
It's important to consider that this code is very new and
|
||||
to some extent still experimental. Please see the docs for
|
||||
current restrictions, caveats, and performance implications.
|
||||
|
||||
Right now it's suitable only for the more enterprising early adopters.
|
||||
|
||||
It does seem to work well for a number of people (including me), but
|
||||
it's still very early and API changes, code reorganisations or further
|
||||
schema changes are still a possibility.
|
||||
It does seem to work very well for a number of people, but
|
||||
API changes, code reorganisations or further schema changes
|
||||
are still a possibility. There may also remain larger bugs
|
||||
and problems in the code that have not yet been found.
|
||||
|
||||
|
||||
Links
|
||||
|
|
|
|||
73
README.rst
73
README.rst
|
|
@ -1,19 +1,12 @@
|
|||
**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
|
||||
polymorphic.py in earnest. Sorry for any inconvenience.
|
||||
This should be the final DB schema now.
|
||||
Release Notes, Usage, Code
|
||||
--------------------------
|
||||
|
||||
* Please see `here for release notes, news and discussion`_ (Google Group)
|
||||
* Installation and usage: `Documentation and Examples`_ (or the short `Overview`_)
|
||||
* The code is on GitHub_ and Bitbucket_ and can also be downloaded as TGZ_ or ZIP_
|
||||
|
||||
Usage, Examples, Installation & Documentation, Links
|
||||
----------------------------------------------------
|
||||
|
||||
* Please see the `Documentation and Examples`_ (or the short `Overview`_)
|
||||
* News & comments: `News, Comments, Questions & Discussion`_
|
||||
* The code can be found on GitHub_ and Bitbucket_, or downloaded as TGZ_ or ZIP_
|
||||
|
||||
.. _Documentation and Examples: http://bserve.webhop.org/wiki/django_polymorphic/doc
|
||||
.. _News, Comments, Questions & Discussion: http://groups.google.de/group/django-polymorphic/topics
|
||||
.. _here for release notes, news and discussion: http://groups.google.de/group/django-polymorphic/topics
|
||||
.. _Documentation and Examples: http://bserve.webhop.org/wiki/django_polymorphic/doc
|
||||
.. _GitHub: http://github.com/bconstantin/django_polymorphic
|
||||
.. _Bitbucket: http://bitbucket.org/bconstantin/django_polymorphic
|
||||
.. _TGZ: http://github.com/bconstantin/django_polymorphic/tarball/master
|
||||
|
|
@ -24,12 +17,15 @@ Usage, Examples, Installation & Documentation, Links
|
|||
What is django_polymorphic good for?
|
||||
------------------------------------
|
||||
|
||||
It causes objects being retrieved from the database to always be returned back
|
||||
with the same type/class and fields they were created and saved with.
|
||||
It implements seamless polymorphic inheritance for Django models.
|
||||
|
||||
Example:
|
||||
If the models ``ArtProject`` and ``ResearchProject`` inherit from the model ``Project``,
|
||||
and we have saved one of each of them into the database, then we can do::
|
||||
This means: objects being retrieved from the database are always returned
|
||||
back with the same type/class and fields they were created and saved with.
|
||||
|
||||
An example:
|
||||
If we defined the model ``Project`` as a base class for our models
|
||||
``ArtProject`` and ``ResearchProject``, and we have stored one of
|
||||
each into the database, then we can do::
|
||||
|
||||
>>> Project.objects.all()
|
||||
.
|
||||
|
|
@ -41,23 +37,40 @@ It doesn't matter how these objects are retrieved: be it through the
|
|||
model's own managers/querysets, ForeignKeys, ManyToManyFields
|
||||
or OneToOneFields.
|
||||
|
||||
``django_polymorphic`` does this only for models that explicitly request
|
||||
this behaviour (and implicitely for their submodels).
|
||||
As seen in this example, the resulting querysets are polymorphic,
|
||||
and will typically deliver objects of several different types in
|
||||
a single query result.
|
||||
|
||||
The resulting querysets are polymorphic, i.e they may deliver
|
||||
objects of several different types in a single query result.
|
||||
django_polymorphic does this only for models that explicitely enable it
|
||||
(and for their submodels).
|
||||
|
||||
Please see the `Documentation and Examples`_ for more information
|
||||
(also included as the file ``DOCS.rst`` with the source).
|
||||
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
It's important to consider that this code is still very new and
|
||||
experimental. Please see the docs for current restrictions, caveats,
|
||||
and performance implications.
|
||||
It's important to consider that this code is very new and
|
||||
to some extent still experimental. Please see the docs for
|
||||
current restrictions, caveats, and performance implications.
|
||||
|
||||
It does seem to work well for a number of people (including me), but
|
||||
it's still very early and API changes, code reorganisations or further
|
||||
schema changes are still a possibility.
|
||||
It does seem to work very well for a number of people, but
|
||||
API changes, code reorganisations or further schema changes
|
||||
are still a possibility. There may also remain larger bugs
|
||||
and problems in the code that have not yet been found.
|
||||
|
||||
Right now it's suitable only for the more enterprising early adopters.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
django_polymorphic uses the same license as Django (BSD-like).
|
||||
|
||||
|
||||
Database Schema Change on January 26
|
||||
------------------------------------
|
||||
|
||||
| The update from January 26 changed the database schema (more info in the commit-log_).
|
||||
| Sorry for any inconvenience. But this should be the final DB schema now.
|
||||
|
||||
.. _commit-log: http://github.com/bconstantin/django_polymorphic/commit/c2b420aea06637966a208329ef7ec853889fa4c7
|
||||
|
|
|
|||
|
|
@ -16,3 +16,11 @@ class ModelB(ModelA):
|
|||
field2 = models.CharField(max_length=10)
|
||||
class ModelC(ModelB):
|
||||
field3 = models.CharField(max_length=10)
|
||||
|
||||
class SModelA(ShowFieldsAndTypes, PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class SModelB(SModelA):
|
||||
field2 = models.CharField(max_length=10)
|
||||
class SModelC(SModelB):
|
||||
field3 = models.CharField(max_length=10)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Fully Polymorphic Django Models
|
||||
===============================
|
||||
|
||||
Please see the examples and documentation here:
|
||||
For an overview, examples, documentation and updates please see here:
|
||||
|
||||
http://bserve.webhop.org/wiki/django_polymorphic
|
||||
|
||||
|
|
@ -62,6 +62,10 @@ class PolymorphicManager(models.Manager):
|
|||
###################################################################################
|
||||
### PolymorphicQuerySet
|
||||
|
||||
# PolymorphicQuerySet Q objects (and filter()) support these additional key words.
|
||||
# These are forbidden as field names (a descriptive exception is raised)
|
||||
POLYMORPHIC_SPECIAL_Q_KWORDS = [ 'instance_of', 'not_instance_of']
|
||||
|
||||
class PolymorphicQuerySet(QuerySet):
|
||||
"""
|
||||
QuerySet for PolymorphicModel
|
||||
|
|
@ -73,15 +77,19 @@ class PolymorphicQuerySet(QuerySet):
|
|||
"""
|
||||
|
||||
def instance_of(self, *args):
|
||||
"""Filter the queryset to only include the classes in args (and their subclasses).
|
||||
Implementation in _translate_polymorphic_filter_defnition."""
|
||||
return self.filter(instance_of=args)
|
||||
|
||||
def not_instance_of(self, *args):
|
||||
"""Filter the queryset to exclude the classes in args (and their subclasses).
|
||||
Implementation in _translate_polymorphic_filter_defnition."""
|
||||
return self.filter(not_instance_of=args)
|
||||
|
||||
def _filter_or_exclude(self, negate, *args, **kwargs):
|
||||
""" we override this internal Django functon since it is used for all filtering """
|
||||
_translate_polymorphic_filter_defnitions_in_args(self.model, args) # the Q objects
|
||||
additional_args = _translate_polymorphic_filter_defnitions_in_kwargs(self.model, kwargs) # filter_field='data'
|
||||
"We override this internal Django functon as it is used for all filter member functions."
|
||||
_translate_polymorphic_filter_definitions_in_args(self.model, args) # the Q objects
|
||||
additional_args = _translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs) # filter_field='data'
|
||||
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs)
|
||||
|
||||
# these queryset functions are not yet supported
|
||||
|
|
@ -154,13 +162,13 @@ class PolymorphicQuerySet(QuerySet):
|
|||
def iterator(self):
|
||||
"""
|
||||
This function is used by Django for all object retrieval.
|
||||
By overriding it, we modify the objects that ths queryset returns
|
||||
when it is evaluated (or it's get method or other object-returning methods are called).
|
||||
By overriding it, we modify the objects that this queryset returns
|
||||
when it is evaluated (or its get method or other object-returning methods are called).
|
||||
|
||||
Here we do the same as:
|
||||
|
||||
base_result_objects=list(super(PolymorphicQuerySet, self).iterator())
|
||||
real_results=self._get_get_real_instances(base_result_objects)
|
||||
real_results=self._get_real_instances(base_result_objects)
|
||||
for o in real_results: yield o
|
||||
|
||||
but it requests the objects in chunks from the database,
|
||||
|
|
@ -196,9 +204,9 @@ class PolymorphicQuerySet(QuerySet):
|
|||
# These functions implement the additional filter- and Q-object functionality.
|
||||
# They form a kind of small framework for easily adding more
|
||||
# functionality to filters and Q objects.
|
||||
# Probably a more general queryset enhancement class could be made out them.
|
||||
# Probably a more general queryset enhancement class could be made out of them.
|
||||
|
||||
def _translate_polymorphic_filter_defnitions_in_kwargs(queryset_model, kwargs):
|
||||
def _translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs):
|
||||
"""
|
||||
Translate the keyword argument list for PolymorphicQuerySet.filter()
|
||||
|
||||
|
|
@ -214,8 +222,9 @@ def _translate_polymorphic_filter_defnitions_in_kwargs(queryset_model, kwargs):
|
|||
"""
|
||||
additional_args = []
|
||||
for field_path, val in kwargs.items():
|
||||
# normal filter expression => ignore
|
||||
|
||||
new_expr = _translate_polymorphic_filter_defnition(queryset_model, field_path, val)
|
||||
|
||||
if type(new_expr) == tuple:
|
||||
# replace kwargs element
|
||||
del(kwargs[field_path])
|
||||
|
|
@ -227,7 +236,7 @@ def _translate_polymorphic_filter_defnitions_in_kwargs(queryset_model, kwargs):
|
|||
|
||||
return additional_args
|
||||
|
||||
def _translate_polymorphic_filter_defnitions_in_args(queryset_model, args):
|
||||
def _translate_polymorphic_filter_definitions_in_args(queryset_model, args):
|
||||
"""
|
||||
Translate the non-keyword argument list for PolymorphicQuerySet.filter()
|
||||
|
||||
|
|
@ -288,7 +297,7 @@ def _translate_polymorphic_filter_defnition(queryset_model, field_path, field_va
|
|||
|
||||
def _translate_polymorphic_field_path(queryset_model, field_path):
|
||||
"""
|
||||
Translate a field path from keyword argument, as used for
|
||||
Translate a field path from a keyword argument, as used for
|
||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||
|
||||
E.g.: ModelC___field3 is translated into modela__modelb__modelc__field3
|
||||
|
|
@ -412,10 +421,13 @@ class PolymorphicModelBase(ModelBase):
|
|||
|
||||
def __new__(self, model_name, bases, attrs):
|
||||
#print; print '###', model_name, '- bases:', bases
|
||||
|
||||
|
||||
# create new model
|
||||
new_class = self.call_superclass_new_method(model_name, bases, attrs)
|
||||
|
||||
# check if the model fields are all allowed
|
||||
self.validate_model_fields(new_class)
|
||||
|
||||
# create list of all managers to be inherited from the base classes
|
||||
inherited_managers = new_class.get_inherited_managers(attrs)
|
||||
|
||||
|
|
@ -494,6 +506,13 @@ class PolymorphicModelBase(ModelBase):
|
|||
if do_app_label_workaround: del(meta.app_label)
|
||||
return new_class
|
||||
|
||||
def validate_model_fields(self):
|
||||
"check if all fields names are allowed (i.e. not in POLYMORPHIC_SPECIAL_Q_KWORDS)"
|
||||
for f in self._meta.fields:
|
||||
if f.name in POLYMORPHIC_SPECIAL_Q_KWORDS:
|
||||
e = 'PolymorphicModel: "%s" - field name "%s" is not allowed in polymorphic models'
|
||||
raise AssertionError(e % (self.__name__, f.name) )
|
||||
|
||||
@classmethod
|
||||
def validate_model_manager(self, manager, model_name, manager_name):
|
||||
"""check if the manager is derived from PolymorphicManager
|
||||
|
|
@ -638,7 +657,7 @@ class PolymorphicModel(models.Model):
|
|||
|
||||
|
||||
class ShowFields(object):
|
||||
""" mixin that shows the object's class, it's fields and field contents """
|
||||
""" model 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:
|
||||
|
|
@ -654,7 +673,7 @@ class ShowFields(object):
|
|||
|
||||
|
||||
class ShowFieldsAndTypes(object):
|
||||
""" like ShowFields, but also show field types """
|
||||
""" model mixin, 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:
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ class MgrInheritB(MgrInheritA):
|
|||
class MgrInheritC(ShowFieldsAndTypes, MgrInheritB):
|
||||
pass
|
||||
|
||||
# test bad field name
|
||||
#class TestBadFieldModel(PolymorphicModel):
|
||||
# instance_of = models.CharField(max_length=10)
|
||||
|
||||
# validation error: "polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes
|
||||
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
|
||||
# fixed with related_name
|
||||
|
|
|
|||
Loading…
Reference in New Issue