8000 Fixed #23615 -- Validate that a Model instance's "check" attribute is… · alex-python/django@a5c7741 · GitHub
[go: up one dir, main page]

Skip to content

Commit a5c7741

Browse files
Rigel Di Scalaloic
authored andcommitted
Fixed #23615 -- Validate that a Model instance's "check" attribute is a method.
The "check" name is a reserved word used by Django's check framework, and cannot be redefined as something else other than a method, or the check framework will raise an error. This change amends the django.core.checks.model_check.check_all_models() function, so that it verifies that a model instance's attribute "check" is actually a method. This new check is assigned the id "models.E020".
1 parent 157f9cf commit a5c7741

File tree

4 files changed

+83
-10
lines changed

4 files changed

+83
-10
lines changed

django/core/checks/model_checks.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
33

4-
from itertools import chain
4+
import inspect
55
import types
66

77
from django.apps import apps
8-
9-
from . import Error, Tags, register
8+
from django.core.checks import Error, Tags, register
109

1110

1211
@register(Tags.models)
1312
def check_all_models(app_configs=None, **kwargs):
14-
errors = [model.check(**kwargs)
15-
for model in apps.get_models()
16-
if app_configs is None or model._meta.app_config in app_configs]
17-
return list(chain(*errors))
13+
errors = []
14+
for model in apps.get_models():
15+
if app_configs is None or model._meta.app_config in app_configs:
16+
if not inspect.ismethod(model.check):
17+
errors.append(
18+
Error(
19+
"The '%s.check()' class method is "
20+
"currently overridden by %r." % (
21+
model.__name__, model.check),
22+
hint=None,
23+
obj=model,
24+
id='models.E020'
25+
)
26+
)
27+
else:
28+
errors.extend(model.check(**kwargs))
29+
return errors
1830

1931

2032
@register(Tags.models, Tags.signals)
2133
def check_model_signals(app_configs=None, **kwargs):
22-
"""Ensure lazily referenced model signals senders are installed."""
34+
"""
35+
Ensure lazily referenced model signals senders are installed.
36+
"""
37+
# Avoid circular import
2338
from django.db import models
24-
errors = []
2539

40+
errors = []
2641
for name in dir(models.signals):
2742
obj = getattr(models.signals, name)
2843
if isinstance(obj, models.signals.ModelSignal):

docs/ref/checks.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Models
6464
* **models.E019**: Autogenerated column name too long for M2M field
6565
``<M2M field>``. Maximum length is ``<maximum length>`` for database
6666
``<alias>``.
67+
* **models.E020**: The ``<model>.check()`` class method is currently overridden.
6768

6869
Fields
6970
~~~~~~
@@ -94,7 +95,6 @@ Fields
9495
are mutually exclusive. Only one of these options may be present.
9596
* **fields.W161**: Fixed default value provided.
9697

97-
9898
File Fields
9999
~~~~~~~~~~~
100100

docs/releases/1.7.1.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,6 @@ Bugfixes
120120

121121
* Fixed a crash while parsing cookies containing invalid content
122122
(:ticket:`23638`).
123+
124+
* The system check framework now raises error **models.E020** when the
125+
class method ``Model.check()`` is unreachable (:ticket:`23615`).

tests/check_framework/tests.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
from django.conf import settings
99
from django.core import checks
1010
from django.core.checks import Error, Warning
11+
from django.core.checks.model_checks import check_all_models
1112
from django.core.checks.registry import CheckRegistry
1213
from django.core.checks.compatibility.django_1_6_0 import check_1_6_compatibility
1314
from django.core.checks.compatibility.django_1_7_0 import check_1_7_compatibility
1415
from django.core.management.base import CommandError
1516
from django.core.management import call_command
17+
from django.db import models
1618
from django.db.models.fields import NOT_PROVIDED
1719
from django.test import TestCase
1820
from django.test.utils import override_settings, override_system_checks
@@ -327,3 +329,56 @@ def test_silenced_warning(self):
327329

328330
self.assertEqual(out.getvalue(), 'System check identified no issues (1 silenced).\n')
329331
self.assertEqual(err.getvalue(), '')
332+
333+
334+
class CheckFrameworkReservedNamesTests(TestCase):
335+
336+
def setUp(self):
337+
self.current_models = apps.all_models[__package__]
338+
self.saved_models = set(self.current_models)
339+
340+
def tearDown(self):
341+
for model in (set(self.current_models) - self.saved_models):
342+
del self.current_models[model]
343+
apps.clear_cache()
344+
345+
@override_settings(SILENCED_SYSTEM_CHECKS=['models.E020'])
346+
def test_model_check_method_not_shadowed(self):
347+
class ModelWithAttributeCalledCheck(models.Model):
348+
check = 42
349+
350+
class ModelWithFieldCalledCheck(models.Model):
351+
check = models.IntegerField()
352+
353+
class ModelWithRelatedManagerCalledCheck(models.Model):
354+
pass
355+
356+
class ModelWithDescriptorCalledCheck(models.Model):
357+
check = models.ForeignKey(ModelWithRelatedManagerCalledCheck)
358+
article = models.ForeignKey(ModelWithRelatedManagerCalledCheck, related_name='check')
359+
360+
expected = [
361+
Error(
362+
"The 'ModelWithAttributeCalledCheck.check()' class method is "
363+
"currently overridden by 42.",
364+
hint=None,
365+
obj=ModelWithAttributeCalledCheck,
366+
id='models.E020'
367+
),
368+
Error(
369+
"The 'ModelWithRelatedManagerCalledCheck.check()' class method is "
370+
"currently overridden by %r." % ModelWithRelatedManagerCalledCheck.check,
371+
hint=None,
372+
obj=ModelWithRelatedManagerCalledCheck,
373+
id='models.E020'
374+
),
375+
Error(
376+
"The 'ModelWithDescriptorCalledCheck.check()' class method is "
377+
"currently overridden by %r." % ModelWithDescriptorCalledCheck.check,
378+
hint=None,
379+
obj=ModelWithDescriptorCalledCheck,
380+
id='models.E020'
381+
),
382+
]
383+
384+
self.assertEqual(check_all_models(), expected)

0 commit comments

Comments
 (0)
0