doc- and minor code updates

fix_request_path_info
Bert Constantin 2010-01-30 20:30:37 +01:00
parent c7ac78e08d
commit 2a740e9b10
5 changed files with 138 additions and 93 deletions

View File

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

View File

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

View File

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

View File

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

View File

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