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
*~
.project
.pydevproject
.settings
nbproject
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
*.pyo
*.mo
*.db
*.egg-info/
*.egg/
.coverage
bin/
distribute-0.6.10.tar.gz
htmlcov/
include/
lib/
MANIFEST
dist/
*.egg-info
.project
.idea/
.pydevproject
.idea/workspace.xml
.tox/
.DS_Store
dist/
docs/_build/
htmlcov/

View File

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

View File

@ -1,17 +1,23 @@
Main authors (commit rights to the main repository)
===================================================
* Bert Constantin 2009/2010 (disappeared :( )
* Chris Glass <tribaal@gmail.com> (Current maintainer)
* Chris Glass
* Diederik van der Boor
Contributors
=============
* Adam Wentz
* Andrew Ingram (contributed setup.py)
* Adam Wentz
* Ben Konrath
* Charles Leifer (python 2.4 compatibility)
* Diederik van der Boor (polymorphic admin interface)
* Germán M. Bravo
* Jedediah Smith (proxy models support)
* 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 polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from pexp.models import *
@ -8,6 +8,7 @@ class ProjectChildAdmin(PolymorphicChildModelAdmin):
class ProjectAdmin(PolymorphicParentModelAdmin):
base_model = Project
list_filter = (PolymorphicChildModelFilter,)
child_models = (
(Project, ProjectChildAdmin),
(ArtProject, ProjectChildAdmin),
@ -23,6 +24,7 @@ class ModelAChildAdmin(PolymorphicChildModelAdmin):
class ModelAAdmin(PolymorphicParentModelAdmin):
base_model = ModelA
list_filter = (PolymorphicChildModelFilter,)
child_models = (
(ModelA, ModelAChildAdmin),
(ModelB, ModelAChildAdmin),
@ -38,6 +40,7 @@ if 'Model2A' in globals():
class Model2AAdmin(PolymorphicParentModelAdmin):
base_model = Model2A
list_filter = (PolymorphicChildModelFilter,)
child_models = (
(Model2A, Model2AChildAdmin),
(Model2B, Model2AChildAdmin),
@ -53,6 +56,7 @@ if 'UUIDModelA' in globals():
class UUIDModelAAdmin(PolymorphicParentModelAdmin):
base_model = UUIDModelA
list_filter = (PolymorphicChildModelFilter,)
child_models = (
(UUIDModelA, UUIDModelAChildAdmin),
(UUIDModelB, UUIDModelAChildAdmin),
@ -61,3 +65,16 @@ if 'UUIDModelA' in globals():
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)
class UUIDModelC(UUIDModelB):
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.
Please see LICENSE and AUTHORS for more information.
"""
import django
from polymorphic_model import PolymorphicModel
from manager import PolymorphicManager
from query import PolymorphicQuerySet
@ -29,32 +30,33 @@ Release logic:
__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.utils.encoding import smart_unicode
def get_for_model(self, model, for_concrete_model=True):
from django.utils.encoding import smart_unicode
from django.contrib.contenttypes.models import ContentTypeManager
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
key = (opts.app_label, opts.object_name.lower())
try:
ct = self.__class__._cache[self.db][key]
ct = self._get_from_cache(opts)
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(
app_label=opts.app_label,
model=opts.object_name.lower(),
defaults={'name': smart_unicode(opts.verbose_name_raw)},
app_label = opts.app_label,
model = opts.object_name.lower(),
defaults = {'name': smart_unicode(opts.verbose_name_raw)},
)
self._add_to_cache(self.db, 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.translation import ugettext_lazy as _
__all__ = ('PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin', 'PolymorphicChildModelAdmin')
__all__ = (
'PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin',
'PolymorphicChildModelAdmin', 'PolymorphicChildModelFilter'
)
class RegistrationClosed(RuntimeError):
@ -44,6 +47,31 @@ class PolymorphicModelChoiceForm(forms.Form):
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):
"""
@ -140,7 +168,7 @@ class PolymorphicParentModelAdmin(admin.ModelAdmin):
"""
choices = []
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))
return choices

View File

@ -8,6 +8,7 @@ import inspect
from django.db import models
from django.db.models.base import ModelBase
from django.db.models.manager import ManagerDescriptor
from manager import PolymorphicManager
from query import PolymorphicQuerySet
@ -16,6 +17,11 @@ from query import PolymorphicQuerySet
# These are forbidden as field names (a descriptive exception is raised)
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
@ -63,7 +69,8 @@ class PolymorphicModelBase(ModelBase):
new_class.add_to_class(mgr_name, new_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:
def_mgr = user_manager._copy_to_model(new_class)
#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),
skip managers that are overwritten by the user with same-named class attributes (in attrs)
"""
#print "** ", self.__name__
add_managers = []
add_managers_keys = set()
for base in self.__mro__[1:]:
@ -102,9 +110,23 @@ class PolymorphicModelBase(ModelBase):
for key, manager in base.__dict__.items():
if type(manager) == models.manager.ManagerDescriptor:
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):
continue
if key in ['_base_manager']:
if key == '_base_manager':
continue # let Django handle _base_manager
if key in attrs:
continue
@ -112,25 +134,36 @@ class PolymorphicModelBase(ModelBase):
continue # manager with that name already added, skip
if manager._inherited:
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
self.validate_model_manager(manager, self.__name__, key)
add_managers.append((base.__name__, key, manager))
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
@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 = []
for key, val in self.__dict__.items():
item = getattr(self, key)
if not isinstance(item, models.Manager): continue
mgr_list.append((item.creation_counter, key, item))
for key, val in new_class.__dict__.items():
if isinstance(val, ManagerDescriptor):
val = val.manager
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 mgr_list:
_, 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'
# .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 None

View File

@ -2,7 +2,7 @@
""" PolymorphicManager
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
"""
import warnings
from django.db import models
from polymorphic.query import PolymorphicQuerySet
@ -14,17 +14,23 @@ class PolymorphicManager(models.Manager):
Usually not explicitly needed, except if a custom manager or
a custom queryset class is to be used.
"""
# Tell Django that related fields also need to use this manager:
use_for_related_fields = True
queryset_class = PolymorphicQuerySet
def __init__(self, queryset_class=None, *args, **kwrags):
if not queryset_class:
self.queryset_class = PolymorphicQuerySet
else:
# Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__.
# However, this doesn't work for related managers which instantiate a new version of this class.
# 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
super(PolymorphicManager, self).__init__(*args, **kwrags)
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
# 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
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()
base_objects = models.Manager()
@ -83,7 +85,7 @@ class PolymorphicModel(models.Model):
(used by PolymorphicQuerySet._get_real_instances)
"""
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):
"""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
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):
"""Normally not needed.
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
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
@ -135,25 +151,31 @@ class PolymorphicQuerySet(QuerySet):
# - also record the correct result order in "ordered_id_list"
# - store objects that already have the correct class into "results"
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:
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=...)
assert not base_object.pk in base_result_objects_by_id, (
"django_polymorphic: result objects do not have unique primary keys - model " + unicode(self.model)
)
if not base_object.pk in base_result_objects_by_id:
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_content_type_id):
if base_object.polymorphic_ctype_id == self_model_class_id:
# Real class is exactly the same as base class, go straight to results
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:
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
# 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 extra() select fields from the base objects to the real objects.
# TODO: defer(), only(): support for these would be around here
for modelclass, idlist in idlist_per_model.items():
qs = modelclass.base_objects.filter(pk__in=idlist) # use pk__in instead ####
qs.dup_select_related(self) # copy select related configuration to new qs
for real_concrete_class, idlist in idlist_per_model.items():
real_objects = real_concrete_class.base_objects.filter(pk__in=idlist) # use pk__in instead ####
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
for o in qs:
o_pk = getattr(o, pk_name)
for real_object in real_objects:
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:
for anno_field_name in self.query.aggregates.keys():
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:
for select_field_name in self.query.extra_select.keys():
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
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)
if self.query.aggregates:
annotate_names = self.query.aggregates.keys() # get annotate field list
for o in resultlist:
o.polymorphic_annotate_names = annotate_names
for real_object in resultlist:
real_object.polymorphic_annotate_names = annotate_names
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
if self.query.extra_select:
extra_select_names = self.query.extra_select.keys() # get extra select field list
for o in resultlist:
o.polymorphic_extra_select_names = extra_select_names
for real_object in resultlist:
real_object.polymorphic_extra_select_names = extra_select_names
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'
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__():
q = q | q_class_with_subclasses(subclass)
return q

View File

@ -4,13 +4,14 @@
"""
import uuid
import re
from django.db.models.query import QuerySet
from django.test import TestCase
from django.db.models import Q,Count
from django.db import models
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.tools_for_tests import UUIDField
@ -81,7 +82,7 @@ class DiamondXY(DiamondX, DiamondY):
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
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')
class RelationA(RelationBase):
field_a = models.CharField(max_length=10)
@ -100,9 +101,16 @@ class One2OneRelatingModel(PolymorphicModel):
class One2OneRelatingModelDerived(One2OneRelatingModel):
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):
queryset_class = MyManagerQuerySet
def get_query_set(self):
return super(MyManager, self).get_query_set().order_by('-field1')
class ModelWithMyManager(ShowFieldTypeAndContent, Model2A):
objects = MyManager()
field4 = models.CharField(max_length=10)
@ -117,6 +125,33 @@ class MROBase3(models.Model):
class MRODerived(MROBase2, MROBase3):
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):
mgrA = models.Manager()
mgrA2 = models.Manager()
@ -161,7 +196,6 @@ class Middle(Top):
class Bottom(Middle):
author = models.CharField(max_length=50)
class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel):
uuid_primary_key = UUIDField(primary_key = True)
topic = models.CharField(max_length = 30)
@ -178,6 +212,24 @@ class UUIDPlainB(UUIDPlainA):
class UUIDPlainC(UUIDPlainB):
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
#class TestBadFieldModel(ShowFieldType, PolymorphicModel):
@ -194,7 +246,6 @@ class PolymorphicTests(TestCase):
"""
The test suite
"""
def test_diamond_inheritance(self):
# Django diamond problem
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(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(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(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(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='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[1]), '<ModelWithMyManager: id 5, field1 (CharField) "D1a", field4 (CharField) "D4a">')
self.assertEqual(len(objects), 2)
self.assertEqual(repr(type(ModelWithMyManager.objects)), "<class 'polymorphic.tests.MyManager'>")
self.assertEqual(repr(type(ModelWithMyManager._default_manager)), "<class 'polymorphic.manager.PolymorphicManager'>")
self.assertIs(type(ModelWithMyManager.objects), MyManager)
self.assertIs(type(ModelWithMyManager._default_manager), MyManager)
self.assertIs(type(ModelWithMyManager.base_objects), models.Manager)
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
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
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):