Merge branch 'master' of github.com:chrisglass/django_polymorphic
commit
e38b7002f4
|
|
@ -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/
|
||||||
|
|
|
||||||
12
.travis.yml
12
.travis.yml
|
|
@ -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
|
|
||||||
|
|
|
||||||
12
AUTHORS.rst
12
AUTHORS.rst
|
|
@ -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 :( )
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.*
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue