Merge branch 'master' of github.com:chrisglass/django_polymorphic

fix_request_path_info
Chris Glass 2013-04-10 14:10:15 +02:00
commit e38b7002f4
13 changed files with 382 additions and 125 deletions

49
.gitignore vendored
View File

@ -1,39 +1,16 @@
.hg
*.svn
*.pyc *.pyc
*~ *.pyo
.project *.mo
.pydevproject *.db
.settings *.egg-info/
nbproject *.egg/
tmp
libraries-local
pushgit
pushhg
pushreg
pbackup
mcmd.py
dbconfig_local.py
diffmanagement
scrapbook.py
pip-log.txt
build
ppreadme.py
ppdocs.py
common.css
screen.css
.coverage .coverage
bin/ .project
distribute-0.6.10.tar.gz .idea/
htmlcov/ .pydevproject
include/ .idea/workspace.xml
lib/
MANIFEST
dist/
*.egg-info
.tox/ .tox/
.DS_Store
dist/
docs/_build/
htmlcov/

View File

@ -1,19 +1,15 @@
language: python language: python
python: python:
- "2.6" - "2.6"
- "2.7" - "2.7"
env: env:
- DJANGO=django==1.4.5 - DJANGO=django==1.4.5
- DJANGO=django==1.5 - DJANGO=django==1.5
install: install:
- "pip install $DJANGO --use-mirrors" - "pip install $DJANGO --use-mirrors"
script: script:
- python ./manage.py test - python runtests.py
branches:
matrix: only:
allow_failures: - master
- env: DJANGO=django==1.5

View File

@ -1,17 +1,23 @@
Main authors (commit rights to the main repository) Main authors (commit rights to the main repository)
=================================================== ===================================================
* Bert Constantin 2009/2010 (disappeared :( ) * Chris Glass
* Chris Glass <tribaal@gmail.com> (Current maintainer) * Diederik van der Boor
Contributors Contributors
============= =============
* Adam Wentz
* Andrew Ingram (contributed setup.py) * Andrew Ingram (contributed setup.py)
* Adam Wentz * Adam Wentz
* Ben Konrath * Ben Konrath
* Charles Leifer (python 2.4 compatibility) * Charles Leifer (python 2.4 compatibility)
* Diederik van der Boor (polymorphic admin interface)
* Germán M. Bravo * Germán M. Bravo
* Jedediah Smith (proxy models support)
* Martin Brochhaus * Martin Brochhaus
Former authors / maintainers
============================
* Bert Constantin 2009/2010 (Original author, disappeared from the internet :( )

View File

@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from pexp.models import * from pexp.models import *
@ -8,6 +8,7 @@ class ProjectChildAdmin(PolymorphicChildModelAdmin):
class ProjectAdmin(PolymorphicParentModelAdmin): class ProjectAdmin(PolymorphicParentModelAdmin):
base_model = Project base_model = Project
list_filter = (PolymorphicChildModelFilter,)
child_models = ( child_models = (
(Project, ProjectChildAdmin), (Project, ProjectChildAdmin),
(ArtProject, ProjectChildAdmin), (ArtProject, ProjectChildAdmin),
@ -23,6 +24,7 @@ class ModelAChildAdmin(PolymorphicChildModelAdmin):
class ModelAAdmin(PolymorphicParentModelAdmin): class ModelAAdmin(PolymorphicParentModelAdmin):
base_model = ModelA base_model = ModelA
list_filter = (PolymorphicChildModelFilter,)
child_models = ( child_models = (
(ModelA, ModelAChildAdmin), (ModelA, ModelAChildAdmin),
(ModelB, ModelAChildAdmin), (ModelB, ModelAChildAdmin),
@ -38,6 +40,7 @@ if 'Model2A' in globals():
class Model2AAdmin(PolymorphicParentModelAdmin): class Model2AAdmin(PolymorphicParentModelAdmin):
base_model = Model2A base_model = Model2A
list_filter = (PolymorphicChildModelFilter,)
child_models = ( child_models = (
(Model2A, Model2AChildAdmin), (Model2A, Model2AChildAdmin),
(Model2B, Model2AChildAdmin), (Model2B, Model2AChildAdmin),
@ -53,6 +56,7 @@ if 'UUIDModelA' in globals():
class UUIDModelAAdmin(PolymorphicParentModelAdmin): class UUIDModelAAdmin(PolymorphicParentModelAdmin):
base_model = UUIDModelA base_model = UUIDModelA
list_filter = (PolymorphicChildModelFilter,)
child_models = ( child_models = (
(UUIDModelA, UUIDModelAChildAdmin), (UUIDModelA, UUIDModelAChildAdmin),
(UUIDModelB, UUIDModelAChildAdmin), (UUIDModelB, UUIDModelAChildAdmin),
@ -61,3 +65,16 @@ if 'UUIDModelA' in globals():
admin.site.register(UUIDModelA, UUIDModelAAdmin) admin.site.register(UUIDModelA, UUIDModelAAdmin)
class ProxyChildAdmin(PolymorphicChildModelAdmin):
base_model = ProxyBase
class ProxyAdmin(PolymorphicParentModelAdmin):
base_model = ProxyBase
list_filter = (PolymorphicChildModelFilter,)
child_models = (
(ProxyA, ProxyChildAdmin),
(ProxyB, ProxyChildAdmin),
)
admin.site.register(ProxyBase, ProxyAdmin)

View File

@ -47,3 +47,26 @@ if 'UUIDField' in globals():
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class UUIDModelC(UUIDModelB): class UUIDModelC(UUIDModelB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
class ProxyBase(PolymorphicModel):
title = models.CharField(max_length=200)
def __unicode__(self):
return u"<ProxyBase[type={0}]: {1}>".format(self.polymorphic_ctype, self.title)
class Meta:
ordering = ('title',)
class ProxyA(ProxyBase):
class Meta:
proxy = True
def __unicode__(self):
return u"<ProxyA: {0}>".format(self.title)
class ProxyB(ProxyBase):
class Meta:
proxy = True
def __unicode__(self):
return u"<ProxyB: {0}>".format(self.title)

View File

@ -6,6 +6,7 @@ Copyright:
This code and affiliated files are (C) by Bert Constantin and individual contributors. This code and affiliated files are (C) by Bert Constantin and individual contributors.
Please see LICENSE and AUTHORS for more information. Please see LICENSE and AUTHORS for more information.
""" """
import django
from polymorphic_model import PolymorphicModel from polymorphic_model import PolymorphicModel
from manager import PolymorphicManager from manager import PolymorphicManager
from query import PolymorphicQuerySet from query import PolymorphicQuerySet
@ -29,32 +30,33 @@ Release logic:
__version__ = "0.4.1.dev0" __version__ = "0.4.1.dev0"
# Proxied models need to have it's own ContentType # Monkey-patch Django < 1.5 to allow ContentTypes for proxy models.
if django.VERSION[:2] < (1, 5):
from django.contrib.contenttypes.models import ContentTypeManager from django.contrib.contenttypes.models import ContentTypeManager
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
def get_for_model(self, model, for_concrete_model=True):
from django.utils.encoding import smart_unicode
if for_concrete_model:
model = model._meta.concrete_model
elif model._deferred:
model = model._meta.proxy_for_model
def get_for_proxied_model(self, model):
"""
Returns the ContentType object for a given model, creating the
ContentType if necessary. Lookups are cached so that subsequent lookups
for the same model don't hit the database.
"""
opts = model._meta opts = model._meta
key = (opts.app_label, opts.object_name.lower())
try: try:
ct = self.__class__._cache[self.db][key] ct = self._get_from_cache(opts)
except KeyError: except KeyError:
# Load or create the ContentType entry. The smart_unicode() is
# needed around opts.verbose_name_raw because name_raw might be a
# django.utils.functional.__proxy__ object.
ct, created = self.get_or_create( ct, created = self.get_or_create(
app_label = opts.app_label, app_label = opts.app_label,
model = opts.object_name.lower(), model = opts.object_name.lower(),
defaults = {'name': smart_unicode(opts.verbose_name_raw)}, defaults = {'name': smart_unicode(opts.verbose_name_raw)},
) )
self._add_to_cache(self.db, ct) self._add_to_cache(self.db, ct)
return ct return ct
ContentTypeManager.get_for_proxied_model = get_for_proxied_model
ContentTypeManager.get_for_model__original = ContentTypeManager.get_for_model
ContentTypeManager.get_for_model = get_for_model

View File

@ -17,7 +17,10 @@ from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
__all__ = ('PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin', 'PolymorphicChildModelAdmin') __all__ = (
'PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin',
'PolymorphicChildModelAdmin', 'PolymorphicChildModelFilter'
)
class RegistrationClosed(RuntimeError): class RegistrationClosed(RuntimeError):
@ -44,6 +47,31 @@ class PolymorphicModelChoiceForm(forms.Form):
self.fields['ct_id'].label = self.type_label self.fields['ct_id'].label = self.type_label
class PolymorphicChildModelFilter(admin.SimpleListFilter):
"""
An admin list filter for the PolymorphicParentModelAdmin which enables
filtering by its child models.
"""
title = _('Content type')
parameter_name = 'polymorphic_ctype'
def lookups(self, request, model_admin):
return model_admin.get_child_type_choices()
def queryset(self, request, queryset):
try:
value = int(self.value())
except TypeError:
value = None
if value:
# ensure the content type is allowed
for choice_value, _ in self.lookup_choices:
if choice_value == value:
return queryset.filter(polymorphic_ctype_id=choice_value)
raise PermissionDenied(
'Invalid ContentType "{0}". It must be registered as child model.'.format(value))
return queryset
class PolymorphicParentModelAdmin(admin.ModelAdmin): class PolymorphicParentModelAdmin(admin.ModelAdmin):
""" """
@ -140,7 +168,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
""" """
choices = [] choices = []
for model, _ in self.get_child_models(): for model, _ in self.get_child_models():
ct = ContentType.objects.get_for_model(model) ct = ContentType.objects.get_for_model(model, for_concrete_model=False)
choices.append((ct.id, model._meta.verbose_name)) choices.append((ct.id, model._meta.verbose_name))
return choices return choices

View File

@ -8,6 +8,7 @@ import inspect
from django.db import models from django.db import models
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.db.models.manager import ManagerDescriptor
from manager import PolymorphicManager from manager import PolymorphicManager
from query import PolymorphicQuerySet from query import PolymorphicQuerySet
@ -16,6 +17,11 @@ from query import PolymorphicQuerySet
# These are forbidden as field names (a descriptive exception is raised) # These are forbidden as field names (a descriptive exception is raised)
POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of'] POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of']
try:
from django.db.models.manager import AbstractManagerDescriptor # Django 1.5
except ImportError:
AbstractManagerDescriptor = None
################################################################################### ###################################################################################
### PolymorphicModel meta class ### PolymorphicModel meta class
@ -63,7 +69,8 @@ class PolymorphicModelBase(ModelBase):
new_class.add_to_class(mgr_name, new_manager) new_class.add_to_class(mgr_name, new_manager)
# get first user defined manager; if there is one, make it the _default_manager # get first user defined manager; if there is one, make it the _default_manager
user_manager = new_class.get_first_user_defined_manager() # this value is used by the related objects, restoring access to custom queryset methods on related objects.
user_manager = self.get_first_user_defined_manager(new_class)
if user_manager: if user_manager:
def_mgr = user_manager._copy_to_model(new_class) def_mgr = user_manager._copy_to_model(new_class)
#print '## add default manager', type(def_mgr) #print '## add default manager', type(def_mgr)
@ -91,6 +98,7 @@ class PolymorphicModelBase(ModelBase):
use correct mro, only use managers with _inherited==False (they are of no use), use correct mro, only use managers with _inherited==False (they are of no use),
skip managers that are overwritten by the user with same-named class attributes (in attrs) skip managers that are overwritten by the user with same-named class attributes (in attrs)
""" """
#print "** ", self.__name__
add_managers = [] add_managers = []
add_managers_keys = set() add_managers_keys = set()
for base in self.__mro__[1:]: for base in self.__mro__[1:]:
@ -102,9 +110,23 @@ class PolymorphicModelBase(ModelBase):
for key, manager in base.__dict__.items(): for key, manager in base.__dict__.items():
if type(manager) == models.manager.ManagerDescriptor: if type(manager) == models.manager.ManagerDescriptor:
manager = manager.manager manager = manager.manager
if AbstractManagerDescriptor is not None:
# Django 1.4 unconditionally assigned managers to a model. As of Django 1.5 however,
# the abstract models don't get any managers, only a AbstractManagerDescriptor as substitute.
# Pretend that the manager is still there, so all code works like it used to.
if type(manager) == AbstractManagerDescriptor and base.__name__ == 'PolymorphicModel':
model = manager.model
if key == 'objects':
manager = PolymorphicManager()
manager.model = model
elif key == 'base_objects':
manager = models.Manager()
manager.model = model
if not isinstance(manager, models.Manager): if not isinstance(manager, models.Manager):
continue continue
if key in ['_base_manager']: if key == '_base_manager':
continue # let Django handle _base_manager continue # let Django handle _base_manager
if key in attrs: if key in attrs:
continue continue
@ -112,25 +134,36 @@ class PolymorphicModelBase(ModelBase):
continue # manager with that name already added, skip continue # manager with that name already added, skip
if manager._inherited: if manager._inherited:
continue # inherited managers (on the bases) have no significance, they are just copies continue # inherited managers (on the bases) have no significance, they are just copies
#print >>sys.stderr,'##',self.__name__, key #print '## {0} {1}'.format(self.__name__, key)
if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers
self.validate_model_manager(manager, self.__name__, key) self.validate_model_manager(manager, self.__name__, key)
add_managers.append((base.__name__, key, manager)) add_managers.append((base.__name__, key, manager))
add_managers_keys.add(key) add_managers_keys.add(key)
# The ordering in the base.__dict__ may randomly change depending on which method is added.
# Make sure base_objects is on top, and 'objects' and '_default_manager' follow afterwards.
# This makes sure that the _base_manager is also assigned properly.
add_managers = sorted(add_managers, key=lambda item: item[2].creation_counter, reverse=True)
return add_managers return add_managers
@classmethod @classmethod
def get_first_user_defined_manager(self): def get_first_user_defined_manager(mcs, new_class):
# See if there is a manager attribute directly stored at this inheritance level.
mgr_list = [] mgr_list = []
for key, val in self.__dict__.items(): for key, val in new_class.__dict__.items():
item = getattr(self, key) if isinstance(val, ManagerDescriptor):
if not isinstance(item, models.Manager): continue val = val.manager
mgr_list.append((item.creation_counter, key, item)) if not isinstance(val, PolymorphicManager) or type(val) is PolymorphicManager:
continue
mgr_list.append((val.creation_counter, key, val))
# if there are user defined managers, use first one as _default_manager # if there are user defined managers, use first one as _default_manager
if mgr_list: if mgr_list:
_, manager_name, manager = sorted(mgr_list)[0] _, manager_name, manager = sorted(mgr_list)[0]
#sys.stderr.write( '\n# first user defined manager for model "{model}":\n# "{mgrname}": {mgr}\n# manager model: {mgrmodel}\n\n' #sys.stderr.write( '\n# first user defined manager for model "{model}":\n# "{mgrname}": {mgr}\n# manager model: {mgrmodel}\n\n'
# .format( model=model_name, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) ) # .format( model=self.__name__, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) )
return manager return manager
return None return None

View File

@ -2,7 +2,7 @@
""" PolymorphicManager """ PolymorphicManager
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
""" """
import warnings
from django.db import models from django.db import models
from polymorphic.query import PolymorphicQuerySet from polymorphic.query import PolymorphicQuerySet
@ -14,17 +14,23 @@ class PolymorphicManager(models.Manager):
Usually not explicitly needed, except if a custom manager or Usually not explicitly needed, except if a custom manager or
a custom queryset class is to be used. a custom queryset class is to be used.
""" """
# Tell Django that related fields also need to use this manager:
use_for_related_fields = True use_for_related_fields = True
queryset_class = PolymorphicQuerySet
def __init__(self, queryset_class=None, *args, **kwrags): def __init__(self, queryset_class=None, *args, **kwrags):
if not queryset_class: # Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__.
self.queryset_class = PolymorphicQuerySet # However, this doesn't work for related managers which instantiate a new version of this class.
else: # Hence, for custom managers the new default is using the 'queryset_class' attribute at class level instead.
if queryset_class:
warnings.warn("Using PolymorphicManager(queryset_class=..) is deprecated; override the queryset_class attribute instead", DeprecationWarning)
# For backwards compatibility, still allow the parameter:
self.queryset_class = queryset_class self.queryset_class = queryset_class
super(PolymorphicManager, self).__init__(*args, **kwrags) super(PolymorphicManager, self).__init__(*args, **kwrags)
def get_query_set(self): def get_query_set(self):
return self.queryset_class(self.model) return self.queryset_class(self.model, using=self._db)
# 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 as PolymorphicModel.objects.* # directly accessible as PolymorphicModel.objects.*

View File

@ -67,6 +67,8 @@ class PolymorphicModel(models.Model):
# some applications want to know the name of the fields that are added to its models # some applications want to know the name of the fields that are added to its models
polymorphic_internal_model_fields = ['polymorphic_ctype'] polymorphic_internal_model_fields = ['polymorphic_ctype']
# Note that Django 1.5 removes these managers because the model is abstract.
# They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers()
objects = PolymorphicManager() objects = PolymorphicManager()
base_objects = models.Manager() base_objects = models.Manager()
@ -83,7 +85,7 @@ class PolymorphicModel(models.Model):
(used by PolymorphicQuerySet._get_real_instances) (used by PolymorphicQuerySet._get_real_instances)
""" """
if not self.polymorphic_ctype_id: if not self.polymorphic_ctype_id:
self.polymorphic_ctype = ContentType.objects.get_for_model(self) self.polymorphic_ctype = ContentType.objects.get_for_model(self, for_concrete_model=False)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""Overridden model save function which supports the polymorphism """Overridden model save function which supports the polymorphism
@ -101,6 +103,12 @@ class PolymorphicModel(models.Model):
# so we use the following version, which uses the CopntentType manager cache # so we use the following version, which uses the CopntentType manager cache
return ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class() return ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class()
def get_real_concrete_instance_class_id(self):
return ContentType.objects.get_for_model(self.get_real_instance_class(), for_concrete_model=True).pk
def get_real_concrete_instance_class(self):
return ContentType.objects.get_for_model(self.get_real_instance_class(), for_concrete_model=True).model_class()
def get_real_instance(self): def get_real_instance(self):
"""Normally not needed. """Normally not needed.
If a non-polymorphic manager (like base_objects) has been used to If a non-polymorphic manager (like base_objects) has been used to

View File

@ -17,6 +17,22 @@ from django.db.models.query import CHUNK_SIZE # this is 100 for Dj
Polymorphic_QuerySet_objects_per_request = CHUNK_SIZE Polymorphic_QuerySet_objects_per_request = CHUNK_SIZE
def transmogrify(cls, obj):
"""
Upcast a class to a different type without asking questions.
"""
if not '__init__' in obj.__dict__:
# Just assign __class__ to a different value.
new = obj
new.__class__ = cls
else:
# Run constructor, reassign values
new = cls()
for k,v in obj.__dict__.items():
new.__dict__[k] = v
return new
################################################################################### ###################################################################################
### PolymorphicQuerySet ### PolymorphicQuerySet
@ -135,25 +151,31 @@ class PolymorphicQuerySet(QuerySet):
# - also record the correct result order in "ordered_id_list" # - also record the correct result order in "ordered_id_list"
# - store objects that already have the correct class into "results" # - store objects that already have the correct class into "results"
base_result_objects_by_id = {} base_result_objects_by_id = {}
self_model_content_type_id = ContentType.objects.get_for_model(self.model).pk self_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=False).pk
self_concrete_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=True).pk
for base_object in base_result_objects: for base_object in base_result_objects:
ordered_id_list.append(base_object.pk) ordered_id_list.append(base_object.pk)
# check if id of the result object occeres more than once - this can happen e.g. with base_objects.extra(tables=...) # check if id of the result object occeres more than once - this can happen e.g. with base_objects.extra(tables=...)
assert not base_object.pk in base_result_objects_by_id, ( if not base_object.pk in base_result_objects_by_id:
"django_polymorphic: result objects do not have unique primary keys - model " + unicode(self.model)
)
base_result_objects_by_id[base_object.pk] = base_object base_result_objects_by_id[base_object.pk] = base_object
# this object is not a derived object and already the real instance => store it right away if base_object.polymorphic_ctype_id == self_model_class_id:
if (base_object.polymorphic_ctype_id == self_model_content_type_id): # Real class is exactly the same as base class, go straight to results
results[base_object.pk] = base_object results[base_object.pk] = base_object
# this object is derived and its real instance needs to be retrieved
# => store it's id into the bin for this model type
else: else:
idlist_per_model[base_object.get_real_instance_class()].append(base_object.pk) real_concrete_class = base_object.get_real_instance_class()
real_concrete_class_id = base_object.get_real_concrete_instance_class_id()
if real_concrete_class_id == self_concrete_model_class_id:
# Real and base classes share the same concrete ancestor,
# upcast it and put it in the results
results[base_object.pk] = transmogrify(real_concrete_class, base_object)
else:
real_concrete_class = ContentType.objects.get_for_id(real_concrete_class_id).model_class()
idlist_per_model[real_concrete_class].append(base_object.pk)
# django's automatic ".pk" field does not always work correctly for # django's automatic ".pk" field does not always work correctly for
# custom fields in derived objects (unclear yet who to put the blame on). # custom fields in derived objects (unclear yet who to put the blame on).
@ -169,24 +191,29 @@ class PolymorphicQuerySet(QuerySet):
# Then we copy the annotate fields from the base objects to the real objects. # Then we copy the annotate fields from the base objects to the real objects.
# Then we copy the extra() select fields from the base objects to the real objects. # Then we copy the extra() select fields from the base objects to the real objects.
# TODO: defer(), only(): support for these would be around here # TODO: defer(), only(): support for these would be around here
for modelclass, idlist in idlist_per_model.items(): for real_concrete_class, idlist in idlist_per_model.items():
qs = modelclass.base_objects.filter(pk__in=idlist) # use pk__in instead #### real_objects = real_concrete_class.base_objects.filter(pk__in=idlist) # use pk__in instead ####
qs.dup_select_related(self) # copy select related configuration to new qs real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
for o in qs: for real_object in real_objects:
o_pk = getattr(o, pk_name) o_pk = getattr(real_object, pk_name)
real_class = real_object.get_real_instance_class()
# If the real class is a proxy, upcast it
if real_class != real_concrete_class:
real_object = transmogrify(real_class, real_object)
if self.query.aggregates: if self.query.aggregates:
for anno_field_name in self.query.aggregates.keys(): for anno_field_name in self.query.aggregates.keys():
attr = getattr(base_result_objects_by_id[o_pk], anno_field_name) attr = getattr(base_result_objects_by_id[o_pk], anno_field_name)
setattr(o, anno_field_name, attr) setattr(real_object, anno_field_name, attr)
if self.query.extra_select: if self.query.extra_select:
for select_field_name in self.query.extra_select.keys(): for select_field_name in self.query.extra_select.keys():
attr = getattr(base_result_objects_by_id[o_pk], select_field_name) attr = getattr(base_result_objects_by_id[o_pk], select_field_name)
setattr(o, select_field_name, attr) setattr(real_object, select_field_name, attr)
results[o_pk] = o results[o_pk] = real_object
# re-create correct order and return result list # re-create correct order and return result list
resultlist = [results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results] resultlist = [results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results]
@ -194,14 +221,14 @@ class PolymorphicQuerySet(QuerySet):
# set polymorphic_annotate_names in all objects (currently just used for debugging/printing) # set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
if self.query.aggregates: if self.query.aggregates:
annotate_names = self.query.aggregates.keys() # get annotate field list annotate_names = self.query.aggregates.keys() # get annotate field list
for o in resultlist: for real_object in resultlist:
o.polymorphic_annotate_names = annotate_names real_object.polymorphic_annotate_names = annotate_names
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing) # set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
if self.query.extra_select: if self.query.extra_select:
extra_select_names = self.query.extra_select.keys() # get extra select field list extra_select_names = self.query.extra_select.keys() # get extra select field list
for o in resultlist: for real_object in resultlist:
o.polymorphic_extra_select_names = extra_select_names real_object.polymorphic_extra_select_names = extra_select_names
return resultlist return resultlist

View File

@ -220,7 +220,7 @@ def _create_model_filter_Q(modellist, not_instance_of=False):
assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model' assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model'
def q_class_with_subclasses(model): def q_class_with_subclasses(model):
q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model)) q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model, for_concrete_model=False))
for subclass in model.__subclasses__(): for subclass in model.__subclasses__():
q = q | q_class_with_subclasses(subclass) q = q | q_class_with_subclasses(subclass)
return q return q

View File

@ -4,13 +4,14 @@
""" """
import uuid import uuid
import re import re
from django.db.models.query import QuerySet
from django.test import TestCase from django.test import TestCase
from django.db.models import Q,Count from django.db.models import Q,Count
from django.db import models from django.db import models
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from polymorphic import PolymorphicModel, PolymorphicManager from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet
from polymorphic import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent from polymorphic import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
from polymorphic.tools_for_tests import UUIDField from polymorphic.tools_for_tests import UUIDField
@ -81,7 +82,7 @@ class DiamondXY(DiamondX, DiamondY):
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel): class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
field_base = models.CharField(max_length=10) field_base = models.CharField(max_length=10)
fk = models.ForeignKey('self', null=True) fk = models.ForeignKey('self', null=True, related_name='relationbase_set')
m2m = models.ManyToManyField('self') m2m = models.ManyToManyField('self')
class RelationA(RelationBase): class RelationA(RelationBase):
field_a = models.CharField(max_length=10) field_a = models.CharField(max_length=10)
@ -100,9 +101,16 @@ class One2OneRelatingModel(PolymorphicModel):
class One2OneRelatingModelDerived(One2OneRelatingModel): class One2OneRelatingModelDerived(One2OneRelatingModel):
field2 = models.CharField(max_length=10) field2 = models.CharField(max_length=10)
class MyManagerQuerySet(PolymorphicQuerySet):
def my_queryset_foo(self):
return self.all() # Just a method to prove the existance of the custom queryset.
class MyManager(PolymorphicManager): class MyManager(PolymorphicManager):
queryset_class = MyManagerQuerySet
def get_query_set(self): def get_query_set(self):
return super(MyManager, self).get_query_set().order_by('-field1') return super(MyManager, self).get_query_set().order_by('-field1')
class ModelWithMyManager(ShowFieldTypeAndContent, Model2A): class ModelWithMyManager(ShowFieldTypeAndContent, Model2A):
objects = MyManager() objects = MyManager()
field4 = models.CharField(max_length=10) field4 = models.CharField(max_length=10)
@ -117,6 +125,33 @@ class MROBase3(models.Model):
class MRODerived(MROBase2, MROBase3): class MRODerived(MROBase2, MROBase3):
pass pass
class ParentModelWithManager(PolymorphicModel):
pass
class ChildModelWithManager(PolymorphicModel):
# Also test whether foreign keys receive the manager:
fk = models.ForeignKey(ParentModelWithManager, related_name='childmodel_set')
objects = MyManager()
class PlainMyManagerQuerySet(QuerySet):
def my_queryset_foo(self):
return self.all() # Just a method to prove the existance of the custom queryset.
class PlainMyManager(models.Manager):
def my_queryset_foo(self):
return self.get_query_set().my_queryset_foo()
def get_query_set(self):
return PlainMyManagerQuerySet(self.model, using=self._db)
class PlainParentModelWithManager(models.Model):
pass
class PlainChildModelWithManager(models.Model):
fk = models.ForeignKey(PlainParentModelWithManager, related_name='childmodel_set')
objects = PlainMyManager()
class MgrInheritA(models.Model): class MgrInheritA(models.Model):
mgrA = models.Manager() mgrA = models.Manager()
mgrA2 = models.Manager() mgrA2 = models.Manager()
@ -161,7 +196,6 @@ class Middle(Top):
class Bottom(Middle): class Bottom(Middle):
author = models.CharField(max_length=50) author = models.CharField(max_length=50)
class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel): class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel):
uuid_primary_key = UUIDField(primary_key = True) uuid_primary_key = UUIDField(primary_key = True)
topic = models.CharField(max_length = 30) topic = models.CharField(max_length = 30)
@ -178,6 +212,24 @@ class UUIDPlainB(UUIDPlainA):
class UUIDPlainC(UUIDPlainB): class UUIDPlainC(UUIDPlainB):
field3 = models.CharField(max_length=10) field3 = models.CharField(max_length=10)
# base -> proxy
class ProxyBase(PolymorphicModel):
some_data = models.CharField(max_length=128)
class ProxyChild(ProxyBase):
class Meta:
proxy = True
# base -> proxy -> real models
class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
name = models.CharField(max_length=10)
class ProxyModelBase(ProxiedBase):
class Meta:
proxy = True
class ProxyModelA(ProxyModelBase):
field1 = models.CharField(max_length=10)
class ProxyModelB(ProxyModelBase):
field2 = models.CharField(max_length=10)
# test bad field name # test bad field name
#class TestBadFieldModel(ShowFieldType, PolymorphicModel): #class TestBadFieldModel(ShowFieldType, PolymorphicModel):
@ -194,7 +246,6 @@ class PolymorphicTests(TestCase):
""" """
The test suite The test suite
""" """
def test_diamond_inheritance(self): def test_diamond_inheritance(self):
# Django diamond problem # Django diamond problem
o1 = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y') o1 = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y')
@ -399,9 +450,11 @@ class PolymorphicTests(TestCase):
self.assertEqual(show_base_manager(PlainA), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainA'>") self.assertEqual(show_base_manager(PlainA), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainA'>")
self.assertEqual(show_base_manager(PlainB), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainB'>") self.assertEqual(show_base_manager(PlainB), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainB'>")
self.assertEqual(show_base_manager(PlainC), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainC'>") self.assertEqual(show_base_manager(PlainC), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainC'>")
self.assertEqual(show_base_manager(Model2A), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.Model2A'>") self.assertEqual(show_base_manager(Model2A), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.Model2A'>")
self.assertEqual(show_base_manager(Model2B), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2B'>") self.assertEqual(show_base_manager(Model2B), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2B'>")
self.assertEqual(show_base_manager(Model2C), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2C'>") self.assertEqual(show_base_manager(Model2C), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2C'>")
self.assertEqual(show_base_manager(One2OneRelatingModel), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.One2OneRelatingModel'>") self.assertEqual(show_base_manager(One2OneRelatingModel), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.One2OneRelatingModel'>")
self.assertEqual(show_base_manager(One2OneRelatingModelDerived), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.One2OneRelatingModelDerived'>") self.assertEqual(show_base_manager(One2OneRelatingModelDerived), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.One2OneRelatingModelDerived'>")
@ -594,23 +647,104 @@ class PolymorphicTests(TestCase):
ModelWithMyManager.objects.create(field1='D1a', field4='D4a') ModelWithMyManager.objects.create(field1='D1a', field4='D4a')
ModelWithMyManager.objects.create(field1='D1b', field4='D4b') ModelWithMyManager.objects.create(field1='D1b', field4='D4b')
objects = ModelWithMyManager.objects.all() objects = ModelWithMyManager.objects.all() # MyManager should reverse the sorting of field1
self.assertEqual(repr(objects[0]), '<ModelWithMyManager: id 6, field1 (CharField) "D1b", field4 (CharField) "D4b">') self.assertEqual(repr(objects[0]), '<ModelWithMyManager: id 6, field1 (CharField) "D1b", field4 (CharField) "D4b">')
self.assertEqual(repr(objects[1]), '<ModelWithMyManager: id 5, field1 (CharField) "D1a", field4 (CharField) "D4a">') self.assertEqual(repr(objects[1]), '<ModelWithMyManager: id 5, field1 (CharField) "D1a", field4 (CharField) "D4a">')
self.assertEqual(len(objects), 2) self.assertEqual(len(objects), 2)
self.assertEqual(repr(type(ModelWithMyManager.objects)), "<class 'polymorphic.tests.MyManager'>") self.assertIs(type(ModelWithMyManager.objects), MyManager)
self.assertEqual(repr(type(ModelWithMyManager._default_manager)), "<class 'polymorphic.manager.PolymorphicManager'>") self.assertIs(type(ModelWithMyManager._default_manager), MyManager)
self.assertIs(type(ModelWithMyManager.base_objects), models.Manager)
def test_manager_inheritance(self): def test_manager_inheritance(self):
self.assertEqual(repr(type(MRODerived.objects)), "<class 'polymorphic.tests.MyManager'>") # MRO # by choice of MRO, should be MyManager from MROBase1.
self.assertIs(type(MRODerived.objects), MyManager)
# check for correct default manager # check for correct default manager
self.assertEqual(repr(type(MROBase1._default_manager)), "<class 'polymorphic.manager.PolymorphicManager'>") self.assertIs(type(MROBase1._default_manager), MyManager)
# Django vanilla inheritance does not inherit MyManager as _default_manager here # Django vanilla inheritance does not inherit MyManager as _default_manager here
self.assertEqual(repr(type(MROBase2._default_manager)), "<class 'polymorphic.manager.PolymorphicManager'>") self.assertIs(type(MROBase2._default_manager), MyManager)
def test_queryset_assignment(self):
# This is just a consistency check for now, testing standard Django behavior.
parent = PlainParentModelWithManager.objects.create()
child = PlainChildModelWithManager.objects.create(fk=parent)
self.assertIs(type(PlainParentModelWithManager._default_manager), models.Manager)
self.assertIs(type(PlainChildModelWithManager._default_manager), PlainMyManager)
self.assertIs(type(PlainChildModelWithManager.objects), PlainMyManager)
self.assertIs(type(PlainChildModelWithManager.objects.all()), PlainMyManagerQuerySet)
# A related set is created using the model's _default_manager, so does gain extra methods.
self.assertIs(type(parent.childmodel_set.my_queryset_foo()), PlainMyManagerQuerySet)
# For polymorphic models, the same should happen.
parent = ParentModelWithManager.objects.create()
child = ChildModelWithManager.objects.create(fk=parent)
self.assertIs(type(ParentModelWithManager._default_manager), PolymorphicManager)
self.assertIs(type(ChildModelWithManager._default_manager), MyManager)
self.assertIs(type(ChildModelWithManager.objects), MyManager)
self.assertIs(type(ChildModelWithManager.objects.my_queryset_foo()), MyManagerQuerySet)
# A related set is created using the model's _default_manager, so does gain extra methods.
self.assertIs(type(parent.childmodel_set.my_queryset_foo()), MyManagerQuerySet)
def test_proxy_models(self):
# prepare some data
for data in ('bleep bloop', 'I am a', 'computer'):
ProxyChild.objects.create(some_data=data)
# this caches ContentType queries so they don't interfere with our query counts later
list(ProxyBase.objects.all())
# one query per concrete class
with self.assertNumQueries(1):
items = list(ProxyBase.objects.all())
self.assertIsInstance(items[0], ProxyChild)
def test_content_types_for_proxy_models(self):
"""Checks if ContentType is capable of returning proxy models."""
from django.db.models import Model
from django.contrib.contenttypes.models import ContentType
ct = ContentType.objects.get_for_model(ProxyChild, for_concrete_model=False)
self.assertEqual(ProxyChild, ct.model_class())
def test_proxy_model_inheritance(self):
"""
Polymorphic abilities should also work when the base model is a proxy object.
"""
# The managers should point to the proper objects.
# otherwise, the whole excersise is pointless.
self.assertEqual(ProxiedBase.objects.model, ProxiedBase)
self.assertEqual(ProxyModelBase.objects.model, ProxyModelBase)
self.assertEqual(ProxyModelA.objects.model, ProxyModelA)
self.assertEqual(ProxyModelB.objects.model, ProxyModelB)
# Create objects
ProxyModelA.objects.create(name="object1")
ProxyModelB.objects.create(name="object2", field2="bb")
# Getting single objects
object1 = ProxyModelBase.objects.get(name='object1')
object2 = ProxyModelBase.objects.get(name='object2')
self.assertEqual(repr(object1), '<ProxyModelA: id 1, name (CharField) "object1", field1 (CharField) "">')
self.assertEqual(repr(object2), '<ProxyModelB: id 2, name (CharField) "object2", field2 (CharField) "bb">')
self.assertIsInstance(object1, ProxyModelA)
self.assertIsInstance(object2, ProxyModelB)
# Same for lists
objects = list(ProxyModelBase.objects.all().order_by('name'))
self.assertEqual(repr(objects[0]), '<ProxyModelA: id 1, name (CharField) "object1", field1 (CharField) "">')
self.assertEqual(repr(objects[1]), '<ProxyModelB: id 2, name (CharField) "object2", field2 (CharField) "bb">')
self.assertIsInstance(objects[0], ProxyModelA)
self.assertIsInstance(objects[1], ProxyModelB)
def test_fix_getattribute(self): def test_fix_getattribute(self):