summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Fulsher <qfulsher@gmail.com>2016-10-11 00:59:17 -0700
committerTim Graham <timograham@gmail.com>2016-11-22 10:14:10 -0500
commitf62abfc03da61977bf080163f0a2ef014decd86a (patch)
treee0b568cee8c2e3be45d9dcb2dc6cca2e53a36b47
parent41759c908240409d44fc11d07a562268c81f4bdf (diff)
Fixed #27295 -- Added a system check to prohibit model names that start or end with an underscore or contain double underscores.
-rw-r--r--django/db/models/base.py30
-rw-r--r--docs/ref/checks.txt4
-rw-r--r--tests/invalid_models_tests/test_models.py39
-rw-r--r--tests/invalid_models_tests/test_relative_fields.py2
4 files changed, 73 insertions, 2 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 1096a58b22..33c562138a 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -1265,7 +1265,11 @@ class Model(six.with_metaclass(ModelBase)):
errors.extend(cls._check_fields(**kwargs))
errors.extend(cls._check_m2m_through_same_relationship())
errors.extend(cls._check_long_column_names())
- clash_errors = cls._check_id_field() + cls._check_field_name_clashes()
+ clash_errors = (
+ cls._check_id_field() +
+ cls._check_field_name_clashes() +
+ cls._check_model_name_db_lookup_clashes()
+ )
errors.extend(clash_errors)
# If there are field name clashes, hide consequent column name
# clashes.
@@ -1470,6 +1474,30 @@ class Model(six.with_metaclass(ModelBase)):
return errors
@classmethod
+ def _check_model_name_db_lookup_clashes(cls):
+ errors = []
+ model_name = cls.__name__
+ if model_name.startswith('_') or model_name.endswith('_'):
+ errors.append(
+ checks.Error(
+ "The model name '%s' cannot start or end with an underscore "
+ "as it collides with the query lookup syntax." % model_name,
+ obj=cls,
+ id='models.E023'
+ )
+ )
+ elif LOOKUP_SEP in model_name:
+ errors.append(
+ checks.Error(
+ "The model name '%s' cannot contain double underscores as "
+ "it collides with the query lookup syntax." % model_name,
+ obj=cls,
+ id='models.E024'
+ )
+ )
+ return errors
+
+ @classmethod
def _check_index_together(cls):
""" Check the value of "index_together" option. """
if not isinstance(cls._meta.index_together, (tuple, list)):
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 638f9af564..a3c659aad5 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -141,6 +141,10 @@ Models
* **models.E022**: ``<function>`` contains a lazy reference to
``<app label>.<model>``, but app ``<app label>`` isn't installed or
doesn't provide model ``<model>``.
+* **models.E023**: The model name ``<model>`` cannot start or end with an
+ underscore as it collides with the query lookup syntax.
+* **models.E024**: The model name ``<model>`` cannot contain double underscores
+ as it collides with the query lookup syntax.
Fields
~~~~~~
diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py
index 1d27e98faa..b50b265bd4 100644
--- a/tests/invalid_models_tests/test_models.py
+++ b/tests/invalid_models_tests/test_models.py
@@ -689,6 +689,45 @@ class OtherModelTests(SimpleTestCase):
self.assertFalse(Child.check())
+ def test_name_beginning_with_underscore(self):
+ class _Model(models.Model):
+ pass
+
+ self.assertEqual(_Model.check(), [
+ Error(
+ "The model name '_Model' cannot start or end with an underscore "
+ "as it collides with the query lookup syntax.",
+ obj=_Model,
+ id='models.E023',
+ )
+ ])
+
+ def test_name_ending_with_underscore(self):
+ class Model_(models.Model):
+ pass
+
+ self.assertEqual(Model_.check(), [
+ Error(
+ "The model name 'Model_' cannot start or end with an underscore "
+ "as it collides with the query lookup syntax.",
+ obj=Model_,
+ id='models.E023',
+ )
+ ])
+
+ def test_name_contains_double_underscores(self):
+ class Test__Model(models.Model):
+ pass
+
+ self.assertEqual(Test__Model.check(), [
+ Error(
+ "The model name 'Test__Model' cannot contain double underscores "
+ "as it collides with the query lookup syntax.",
+ obj=Test__Model,
+ id='models.E024',
+ )
+ ])
+
@override_settings(TEST_SWAPPED_MODEL_BAD_VALUE='not-a-model')
def test_swappable_missing_app_name(self):
class Model(models.Model):
diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py
index d178d6936e..7c20a0fb99 100644
--- a/tests/invalid_models_tests/test_relative_fields.py
+++ b/tests/invalid_models_tests/test_relative_fields.py
@@ -794,7 +794,7 @@ class RelativeFieldTests(SimpleTestCase):
pass
for related_name in related_names:
- Child = type(str('Child_%s') % str(related_name), (models.Model,), {
+ Child = type(str('Child%s') % str(related_name), (models.Model,), {
'parent': models.ForeignKey('Parent', models.CASCADE, related_name=related_name),
'__module__': Parent.__module__,
})