Fixed unwanted manager replacement in Django 1.11 projects.
Django 1.11 uses the old manager inheritance system, unless it's
overwritten with manager_inheritance_from_future. With a class layout
like:
PolymorphicModel (abstract)
PolymorphicMPTTModel (abstract)
GenericCustomer (concrete, has objects = ...)
CustomerGroupBase (abstract, has objects = ...)
Partner (concrete, no manager)
BranchPartner (concrete, no manager)
The last level gets a normal Django Manager instead of the polymorphic
manager. Because the PolymorphicModel had a base_objects manager, this
was typically used as _default_manager. Now that the default manager is
no longer affected, it's also easier to detect why the "objects" doesn't
get the proper manager type. Using "manager_inheritance_from_future" is
recommended instead to have both the right behavior and forward
Django 2.x compatibility.
fix_request_path_info
parent
723c78065c
commit
f898f80594
|
|
@ -7,7 +7,10 @@ from __future__ import absolute_import
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import django
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
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 django.db.models.manager import ManagerDescriptor
|
||||||
|
|
@ -134,33 +137,56 @@ class PolymorphicModelBase(ModelBase):
|
||||||
and its querysets from PolymorphicQuerySet - throw AssertionError if not"""
|
and its querysets from PolymorphicQuerySet - throw AssertionError if not"""
|
||||||
|
|
||||||
if not issubclass(type(manager), PolymorphicManager):
|
if not issubclass(type(manager), PolymorphicManager):
|
||||||
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" manager is of type "' + type(manager).__name__
|
if django.VERSION < (2, 0):
|
||||||
e += '", but must be a subclass of PolymorphicManager'
|
extra = "\nConsider using Meta.manager_inheritance_from_future = True for Django 1.x projects"
|
||||||
raise AssertionError(e)
|
else:
|
||||||
|
extra = ''
|
||||||
|
e = ('PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of'
|
||||||
|
' PolymorphicManager.{extra}'.format(
|
||||||
|
model_name, manager_name, type(manager).__name__, extra=extra))
|
||||||
|
raise ImproperlyConfigured(e)
|
||||||
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet):
|
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet):
|
||||||
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" (PolymorphicManager) has been instantiated with a queryset class which is'
|
e = ('PolymorphicModel: "{0}.{1}" (PolymorphicManager) has been instantiated with a queryset class '
|
||||||
e += ' not a subclass of PolymorphicQuerySet (which is required)'
|
'which is not a subclass of PolymorphicQuerySet (which is required)'.format(model_name, manager_name))
|
||||||
raise AssertionError(e)
|
raise ImproperlyConfigured(e)
|
||||||
return manager
|
return manager
|
||||||
|
|
||||||
# hack: a small patch to Django would be a better solution.
|
@property
|
||||||
# Django's management command 'dumpdata' relies on non-polymorphic
|
def base_objects(self):
|
||||||
# behaviour of the _default_manager. Therefore, we catch any access to _default_manager
|
warnings.warn(
|
||||||
# here and return the non-polymorphic default manager instead if we are called from 'dumpdata.py'
|
"Using PolymorphicModel.base_objects is deprecated.\n"
|
||||||
# Otherwise, the base objects will be upcasted to polymorphic models, and be outputted as such.
|
"Use {0}.objects.non_polymorphic() instead.".format(self.__class__.__name__),
|
||||||
# (non-polymorphic default manager is 'base_objects' for polymorphic models).
|
DeprecationWarning)
|
||||||
# This way we don't need to patch django.core.management.commands.dumpdata
|
|
||||||
# for all supported Django versions.
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == 'dumpdata':
|
|
||||||
# manage.py dumpdata is running
|
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
# Create a manager so the API works as expected. Just don't register it
|
||||||
if name == '_default_manager':
|
# anymore in the Model Meta, so it doesn't substitute our polymorphic
|
||||||
frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name
|
# manager as default manager for the third level of inheritance when
|
||||||
if DUMPDATA_COMMAND in frm[1]:
|
# that third level doesn't define a manager at all.
|
||||||
return self.base_objects
|
manager = models.Manager()
|
||||||
# caller_mod_name = inspect.getmodule(frm[0]).__name__ # does not work with python 2.4
|
manager.name = 'base_objects'
|
||||||
# if caller_mod_name == 'django.core.management.commands.dumpdata':
|
manager.model = self
|
||||||
|
return manager
|
||||||
|
|
||||||
return super(PolymorphicModelBase, self).__getattribute__(name)
|
@property
|
||||||
# TODO: investigate Django how this can be avoided
|
def _default_manager(self):
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'dumpdata':
|
||||||
|
# TODO: investigate Django how this can be avoided
|
||||||
|
# hack: a small patch to Django would be a better solution.
|
||||||
|
# Django's management command 'dumpdata' relies on non-polymorphic
|
||||||
|
# behaviour of the _default_manager. Therefore, we catch any access to _default_manager
|
||||||
|
# here and return the non-polymorphic default manager instead if we are called from 'dumpdata.py'
|
||||||
|
# Otherwise, the base objects will be upcasted to polymorphic models, and be outputted as such.
|
||||||
|
# (non-polymorphic default manager is 'base_objects' for polymorphic models).
|
||||||
|
# This way we don't need to patch django.core.management.commands.dumpdata
|
||||||
|
# for all supported Django versions.
|
||||||
|
frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name
|
||||||
|
if DUMPDATA_COMMAND in frm[1]:
|
||||||
|
return self.base_objects
|
||||||
|
|
||||||
|
manager = super(PolymorphicModelBase, self)._default_manager
|
||||||
|
if not isinstance(manager, PolymorphicManager):
|
||||||
|
warnings.warn("{0}._default_manager is not a PolymorphicManager".format(
|
||||||
|
self.__class__.__name__
|
||||||
|
), RuntimeWarning)
|
||||||
|
|
||||||
|
return manager
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
|
||||||
# Note that Django 1.5 removes these managers because the model is abstract.
|
# 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()
|
# They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers()
|
||||||
objects = PolymorphicManager()
|
objects = PolymorphicManager()
|
||||||
base_objects = models.Manager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,7 @@ class PolymorphicQuerySet(QuerySet):
|
||||||
# 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 real_concrete_class, idlist in idlist_per_model.items():
|
for real_concrete_class, idlist in idlist_per_model.items():
|
||||||
real_objects = real_concrete_class.base_objects.db_manager(self.db).filter(**{
|
real_objects = real_concrete_class.objects.non_polymorphic().using(self.db).filter(**{
|
||||||
('%s__in' % pk_name): idlist,
|
('%s__in' % pk_name): idlist,
|
||||||
})
|
})
|
||||||
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
|
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,9 @@ class MROBase1(ShowFieldType, PolymorphicModel):
|
||||||
|
|
||||||
|
|
||||||
class MROBase2(MROBase1):
|
class MROBase2(MROBase1):
|
||||||
pass # Django vanilla inheritance does not inherit MyManager as _default_manager here
|
class Meta:
|
||||||
|
# Django 1.x inheritance does not inherit MyManager as _default_manager here
|
||||||
|
manager_inheritance_from_future = True
|
||||||
|
|
||||||
|
|
||||||
class MROBase3(models.Model):
|
class MROBase3(models.Model):
|
||||||
|
|
@ -200,7 +202,8 @@ class MROBase3(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class MRODerived(MROBase2, MROBase3):
|
class MRODerived(MROBase2, MROBase3):
|
||||||
pass
|
class Meta:
|
||||||
|
manager_inheritance_from_future = True
|
||||||
|
|
||||||
|
|
||||||
class ParentModelWithManager(PolymorphicModel):
|
class ParentModelWithManager(PolymorphicModel):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue