Skip to content

Commit 7194f91

Browse files
authored
Merge pull request #693 from bckohan/field-error
move model definition errors to system checks
2 parents cb983bf + 79eb39b commit 7194f91

File tree

7 files changed

+88
-27
lines changed

7 files changed

+88
-27
lines changed

docs/changelog.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
Changelog
22
=========
33

4-
.. v4.4.0 (202X-XX-XX)
5-
.. -------------------
4+
v4.4.0 (202X-XX-XX)
5+
-------------------
6+
7+
* Fixed `Change model definition errors to be system checks <https://github.com/jazzband/django-polymorphic/pull/693>`_
68

79
v4.3.0 (2025-12-09)
810
-------------------

src/polymorphic/apps.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from django.apps import AppConfig, apps
2+
from django.core.checks import Error, Tags, register
3+
4+
5+
@register(Tags.models)
6+
def check_reserved_field_names(app_configs, **kwargs):
7+
"""
8+
System check that ensures models don't use reserved field names.
9+
"""
10+
errors = []
11+
12+
# If app_configs is None, check all installed apps
13+
if app_configs is None:
14+
app_configs = apps.get_app_configs()
15+
16+
for app_config in app_configs:
17+
for model in app_config.get_models():
18+
errors.extend(_check_model_reserved_field_names(model))
19+
20+
return errors
21+
22+
23+
def _check_model_reserved_field_names(model):
24+
from polymorphic.base import POLYMORPHIC_SPECIAL_Q_KWORDS
25+
26+
errors = []
27+
28+
for field in model._meta.get_fields():
29+
if field.name in POLYMORPHIC_SPECIAL_Q_KWORDS:
30+
errors.append(
31+
Error(
32+
f"Field '{field.name}' on model '{model.__name__}' is a reserved name.",
33+
obj=field,
34+
id="polymorphic.E001",
35+
)
36+
)
37+
38+
return errors
39+
40+
41+
class PolymorphicConfig(AppConfig):
42+
name = "polymorphic"
43+
verbose_name = "Django Polymorphic"

src/polymorphic/base.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
# PolymorphicQuerySet Q objects (and filter()) support these additional key words.
1717
# These are forbidden as field names (a descriptive exception is raised)
18-
POLYMORPHIC_SPECIAL_Q_KWORDS = ["instance_of", "not_instance_of"]
18+
POLYMORPHIC_SPECIAL_Q_KWORDS = {"instance_of", "not_instance_of"}
1919

2020
DUMPDATA_COMMAND = os.path.join("django", "core", "management", "commands", "dumpdata.py")
2121

@@ -63,9 +63,6 @@ def __new__(cls, model_name, bases, attrs, **kwargs):
6363
# is higher in the MRO
6464
new_class._meta.base_manager_name = "objects"
6565

66-
# check if the model fields are all allowed
67-
cls.validate_model_fields(new_class)
68-
6966
# validate resulting default manager
7067
if not new_class._meta.abstract and not new_class._meta.swapped:
7168
cls.validate_model_manager(new_class.objects, model_name, "objects")
@@ -80,27 +77,6 @@ def __new__(cls, model_name, bases, attrs, **kwargs):
8077

8178
return new_class
8279

83-
@classmethod
84-
def call_superclass_new_method(self, model_name, bases, attrs, **kwargs):
85-
"""call __new__ method of super class and return the newly created class.
86-
Also work around a limitation in Django's ModelBase."""
87-
# There seems to be a general limitation in Django's app_label handling
88-
# regarding abstract models (in ModelBase). See issue 1 on github - TODO: propose patch for Django
89-
# We run into this problem if polymorphic.py is located in a top-level directory
90-
# which is directly in the python path. To work around this we temporarily set
91-
# app_label here for PolymorphicModel.
92-
return super().__new__(self, model_name, bases, attrs, **kwargs)
93-
94-
@classmethod
95-
def validate_model_fields(self, new_class):
96-
"check if all fields names are allowed (i.e. not in POLYMORPHIC_SPECIAL_Q_KWORDS)"
97-
for f in new_class._meta.fields:
98-
if f.name in POLYMORPHIC_SPECIAL_Q_KWORDS:
99-
raise AssertionError(
100-
f'PolymorphicModel: "{new_class.__name__}" - '
101-
f'field name "{f.name}" is not allowed in polymorphic models'
102-
)
103-
10480
@classmethod
10581
def validate_model_manager(cls, manager, model_name, manager_name):
10682
"""check if the manager is derived from PolymorphicManager

src/polymorphic/tests/errata/__init__.py

Whitespace-only changes.

src/polymorphic/tests/errata/migrations/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.db import models
2+
3+
4+
class BadModel(models.Model):
5+
instance_of = models.CharField(max_length=100)
6+
not_instance_of = models.IntegerField()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from django.core.checks import Error, run_checks
2+
from django.test.utils import override_settings
3+
from django.test import SimpleTestCase
4+
5+
6+
@override_settings(
7+
INSTALLED_APPS=[
8+
"polymorphic.tests.errata",
9+
"django.contrib.contenttypes",
10+
"django.contrib.auth",
11+
]
12+
)
13+
class TestErrata(SimpleTestCase):
14+
def test_reserved_field_name_triggers_system_check(self):
15+
"""Test that using reserved field names triggers polymorphic.E001 system check."""
16+
17+
# Run the check function directly on the model
18+
errors = run_checks()
19+
20+
assert len(errors) == 2, f"Expected 2 system check errors but got {len(errors)}: {errors}"
21+
22+
# Verify all errors are the correct type
23+
assert all(isinstance(err, Error) and err.id == "polymorphic.E001" for err in errors), (
24+
f"Expected all errors to have ID 'polymorphic.E001' but got: {errors}"
25+
)
26+
27+
# Verify the error messages mention the correct field names
28+
error_messages = [err.msg for err in errors]
29+
assert any("instance_of" in msg for msg in error_messages), (
30+
f"Expected error for 'instance_of' field but got: {error_messages}"
31+
)
32+
assert any("not_instance_of" in msg for msg in error_messages), (
33+
f"Expected error for 'not_instance_of' field but got: {error_messages}"
34+
)

0 commit comments

Comments
 (0)