Skip to content

Commit 7f44f3a

Browse files
committed
backport fix for #845
1 parent aa873e3 commit 7f44f3a

File tree

11 files changed

+106
-29
lines changed

11 files changed

+106
-29
lines changed

docs/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Changelog
22
=========
33

4+
v4.9.1 (2026-01-15)
5+
-------------------
6+
7+
* Fixed `Meta.base_manager_name is not respected <https://github.com/jazzband/django-polymorphic/issues/845>`_
8+
49
v4.9.0 (2026-01-09)
510
-------------------
611

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "django-polymorphic"
7-
version = "4.9.0"
7+
version = "4.9.1"
88
description = "Seamless polymorphic inheritance for Django models."
99
readme = "README.md"
1010
license = "BSD-3-Clause"

src/polymorphic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
Seamless Polymorphic Inheritance for Django Models
2020
"""
2121

22-
VERSION = "4.9.0"
22+
VERSION = "4.9.1"
2323

2424
__title__ = "Django Polymorphic"
2525
__version__ = VERSION # version synonym for backwards compatibility

src/polymorphic/base.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,28 +71,28 @@ class PolymorphicModelBase(ModelBase):
7171
"""
7272

7373
def __new__(cls, model_name, bases, attrs, **kwargs):
74-
# create new model
75-
new_class = super().__new__(cls, model_name, bases, attrs, **kwargs)
74+
# skip special setup for PolymorphicModel itself
75+
if attrs.pop("_meta_skip", False):
76+
return super().__new__(cls, model_name, bases, attrs, **kwargs)
7677

77-
if new_class._meta.base_manager_name is None:
78-
# by default, use polymorphic manager as the base manager
79-
new_class._meta.base_manager_name = new_class._meta.default_manager_name or "objects"
78+
from .models import PolymorphicModel
8079

81-
# ensure base_manager is a plain PolymorphicManager by resetting it if it
82-
# was not explicitly set and it defaults to a changed default_manager
83-
# the base class manager determination logic is complex enough that we prefer
84-
# to observe its application and correct rather than preempting it
85-
if (
86-
type(new_class._meta.default_manager) is not PolymorphicManager
87-
and new_class._meta.base_manager is new_class._meta.default_manager
80+
new_class = super().__new__(cls, model_name, bases, attrs, **kwargs)
81+
82+
if not any(
83+
parent._meta.base_manager_name
84+
for parent in new_class.mro()
85+
if issubclass(parent, PolymorphicModel)
8886
):
89-
manager = PolymorphicManager()
90-
manager.name = "_base_manager"
91-
manager.model = new_class
92-
manager.auto_created = True
93-
new_class._meta.base_manager_name = None
94-
# write new manager to property cache
95-
new_class._meta.__dict__["base_manager"] = manager
87+
if new_class._meta.default_manager.__class__ is PolymorphicManager:
88+
new_class._meta.__dict__["base_manager"] = new_class._meta.default_manager
89+
else:
90+
manager = PolymorphicManager()
91+
manager.name = "_base_manager"
92+
manager.model = new_class
93+
manager.auto_created = True
94+
# write new manager to property cache
95+
new_class._meta.__dict__["base_manager"] = manager
9696

9797
# validate resulting default manager
9898
if not new_class._meta.abstract and not new_class._meta.swapped:

src/polymorphic/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class PolymorphicModel(models.Model, metaclass=PolymorphicModelBase):
3535
and provides a polymorphic manager as the default manager (and as 'objects').
3636
"""
3737

38+
_meta_skip = True
39+
3840
# for PolymorphicModelBase, so it can tell which models are polymorphic and which are not (duck typing)
3941
polymorphic_model_marker = True
4042

src/polymorphic/tests/deletion/migrations/0001_initial.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2 on 2026-01-09 13:25
1+
# Generated by Django 4.2 on 2026-01-15 16:35
22

33
from decimal import Decimal
44
from django.conf import settings
@@ -12,8 +12,8 @@ class Migration(migrations.Migration):
1212
initial = True
1313

1414
dependencies = [
15-
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
1615
('contenttypes', '0002_remove_content_type_name'),
16+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
1717
]
1818

1919
operations = [

src/polymorphic/tests/examples/views/migrations/0001_initial.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2 on 2026-01-09 13:25
1+
# Generated by Django 4.2 on 2026-01-15 16:35
22

33
from django.db import migrations, models
44
import django.db.models.deletion

src/polymorphic/tests/migrations/0001_initial.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2 on 2026-01-09 13:25
1+
# Generated by Django 4.2 on 2026-01-15 16:35
22

33
from django.conf import settings
44
from django.db import migrations, models
@@ -14,8 +14,8 @@ class Migration(migrations.Migration):
1414
initial = True
1515

1616
dependencies = [
17-
('auth', '0012_alter_user_first_name_max_length'),
1817
('contenttypes', '0002_remove_content_type_name'),
18+
('auth', '0012_alter_user_first_name_max_length'),
1919
]
2020

2121
operations = [
@@ -233,6 +233,26 @@ class Migration(migrations.Migration):
233233
'abstract': False,
234234
},
235235
),
236+
migrations.CreateModel(
237+
name='ManagerTest',
238+
fields=[
239+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
240+
('name', models.CharField(max_length=30)),
241+
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
242+
],
243+
options={
244+
'base_manager_name': 'objects',
245+
},
246+
),
247+
migrations.CreateModel(
248+
name='ManagerTestPlain',
249+
fields=[
250+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
251+
],
252+
options={
253+
'base_manager_name': 'objects',
254+
},
255+
),
236256
migrations.CreateModel(
237257
name='Model2A',
238258
fields=[
@@ -670,6 +690,23 @@ class Migration(migrations.Migration):
670690
},
671691
bases=('tests.m2mthroughbase',),
672692
),
693+
migrations.CreateModel(
694+
name='ManagerTestChild',
695+
fields=[
696+
('managertest_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.managertest')),
697+
],
698+
options={
699+
'abstract': False,
700+
},
701+
bases=('tests.managertest',),
702+
),
703+
migrations.CreateModel(
704+
name='ManagerTestChildPlain',
705+
fields=[
706+
('managertestplain_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='tests.managertestplain')),
707+
],
708+
bases=('tests.managertestplain',),
709+
),
673710
migrations.CreateModel(
674711
name='Middle',
675712
fields=[
@@ -770,7 +807,6 @@ class Migration(migrations.Migration):
770807
bases=(polymorphic.showfields.ShowFieldTypeAndContent, 'tests.model2a'),
771808
managers=[
772809
('my_objects', django.db.models.manager.Manager()),
773-
('objects', django.db.models.manager.Manager()),
774810
],
775811
),
776812
migrations.CreateModel(

src/polymorphic/tests/models.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,3 +1065,30 @@ def natural_key(self):
10651065
return self.foo.natural_key()
10661066

10671067
natural_key.dependencies = ["tests.natkeyparent"]
1068+
1069+
1070+
class ManagerTest(PolymorphicModel):
1071+
name = models.CharField(max_length=30)
1072+
1073+
objects = CustomBaseManager()
1074+
1075+
class Meta:
1076+
base_manager_name = "objects"
1077+
1078+
1079+
class ManagerTestChild(ManagerTest):
1080+
pass
1081+
1082+
1083+
class PlainManager(models.Manager): ...
1084+
1085+
1086+
class ManagerTestPlain(models.Model):
1087+
objects = PlainManager()
1088+
1089+
class Meta:
1090+
base_manager_name = "objects"
1091+
1092+
1093+
class ManagerTestChildPlain(ManagerTestPlain):
1094+
pass

src/polymorphic/tests/test_orm.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ def test_create_instanceof_q(self):
473473
assert dict(q.children) == dict(polymorphic_ctype__in=expected)
474474

475475
def test_base_manager(self):
476-
from .models import CustomBaseManager
476+
from .models import ManagerTest, CustomBaseManager, ManagerTestChild
477477

478478
def base_manager(model):
479479
return (type(model._base_manager), model._base_manager.model)
@@ -503,6 +503,13 @@ def base_manager(model):
503503
assert type(Model2CNamedManagers._base_manager) is CustomBaseManager
504504
assert type(Model2CNamedDefault._base_manager) is PolymorphicManager
505505

506+
assert type(ManagerTest._base_manager) is CustomBaseManager
507+
assert type(ManagerTest._default_manager) is CustomBaseManager
508+
assert ManagerTest._base_manager is ManagerTest._default_manager
509+
assert type(ManagerTestChild._base_manager) is CustomBaseManager
510+
assert type(ManagerTestChild._default_manager) is CustomBaseManager
511+
assert ManagerTestChild._base_manager is ManagerTestChild._default_manager
512+
506513
def test_default_manager(self):
507514
from .models import FilteredManager, FilteredManager2
508515

0 commit comments

Comments
 (0)