diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py index fa63b9e8a7..b37fca9922 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -6,7 +6,9 @@ import warnings from django.conf import settings from django.core.checks import Error +from django.core.checks.model_checks import _check_lazy_references from django.db import connections, models +from django.db.models.signals import post_init from django.test import SimpleTestCase from django.test.utils import isolate_apps, override_settings @@ -827,3 +829,76 @@ class OtherModelTests(SimpleTestCase): id='fields.E340', ) ]) + + @isolate_apps('django.contrib.auth', kwarg_name='apps') + def test_lazy_reference_checks(self, apps): + class DummyModel(models.Model): + author = models.ForeignKey('Author', models.CASCADE) + + class Meta: + app_label = 'invalid_models_tests' + + class DummyClass(object): + def __call__(self, **kwargs): + pass + + def dummy_method(self): + pass + + def dummy_function(*args, **kwargs): + pass + + apps.lazy_model_operation(dummy_function, ('auth', 'imaginarymodel')) + apps.lazy_model_operation(dummy_function, ('fanciful_app', 'imaginarymodel')) + + post_init.connect(dummy_function, sender='missing-app.Model', apps=apps) + post_init.connect(DummyClass(), sender='missing-app.Model', apps=apps) + post_init.connect(DummyClass().dummy_method, sender='missing-app.Model', apps=apps) + + expected = [ + Error( + "%r contains a lazy reference to auth.imaginarymodel, " + "but app 'auth' doesn't provide model 'imaginarymodel'." % dummy_function, + obj=dummy_function, + id='models.E022', + ), + Error( + "%r contains a lazy reference to fanciful_app.imaginarymodel, " + "but app 'fanciful_app' isn't installed." % dummy_function, + obj=dummy_function, + id='models.E022', + ), + Error( + "An instance of class 'DummyClass' was connected to " + "the 'post_init' signal with a lazy reference to the sender " + "'missing-app.model', but app 'missing-app' isn't installed.", + hint=None, + obj='invalid_models_tests.test_models', + id='signals.E001', + ), + Error( + "Bound method 'DummyClass.dummy_method' was connected to the " + "'post_init' signal with a lazy reference to the sender " + "'missing-app.model', but app 'missing-app' isn't installed.", + hint=None, + obj='invalid_models_tests.test_models', + id='signals.E001', + ), + Error( + "The field invalid_models_tests.DummyModel.author was declared " + "with a lazy reference to 'invalid_models_tests.author', but app " + "'invalid_models_tests' isn't installed.", + hint=None, + obj=DummyModel.author.field, + id='fields.E307', + ), + Error( + "The function 'dummy_function' was connected to the 'post_init' " + "signal with a lazy reference to the sender " + "'missing-app.model', but app 'missing-app' isn't installed.", + hint=None, + obj='invalid_models_tests.test_models', + id='signals.E001', + ), + ] + self.assertEqual(_check_lazy_references(apps), expected) diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index 0dff216cb6..2f16e83d18 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -157,6 +157,27 @@ class CharFieldTests(TestCase): ] self.assertEqual(errors, expected) + def test_iterable_of_iterable_choices(self): + class ThingItem(object): + def __init__(self, value, display): + self.value = value + self.display = display + + def __iter__(self): + return (x for x in [self.value, self.display]) + + def __len__(self): + return 2 + + class Things(object): + def __iter__(self): + return (x for x in [ThingItem(1, 2), ThingItem(3, 4)]) + + class ThingWithIterableChoices(models.Model): + thing = models.CharField(max_length=100, blank=True, choices=Things()) + + self.assertEqual(ThingWithIterableChoices._meta.get_field('thing').check(), []) + def test_choices_containing_non_pairs(self): class Model(models.Model): field = models.CharField(max_length=10, choices=[(1, 2, 3), (1, 2, 3)]) diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index 01941fc58c..6756302bf1 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -995,6 +995,21 @@ class AccessorClashTests(SimpleTestCase): ] self.assertEqual(errors, expected) + def test_no_clash_for_hidden_related_name(self): + class Stub(models.Model): + pass + + class ManyToManyRel(models.Model): + thing1 = models.ManyToManyField(Stub, related_name='+') + thing2 = models.ManyToManyField(Stub, related_name='+') + + class FKRel(models.Model): + thing1 = models.ForeignKey(Stub, models.CASCADE, related_name='+') + thing2 = models.ForeignKey(Stub, models.CASCADE, related_name='+') + + self.assertEqual(ManyToManyRel.check(), []) + self.assertEqual(FKRel.check(), []) + @isolate_apps('invalid_models_tests') class ReverseQueryNameClashTests(SimpleTestCase): diff --git a/tests/model_validation/__init__.py b/tests/model_validation/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/model_validation/models.py b/tests/model_validation/models.py deleted file mode 100644 index 0388f9508c..0000000000 --- a/tests/model_validation/models.py +++ /dev/null @@ -1,49 +0,0 @@ -from django.db import models - - -class ThingItem(object): - - def __init__(self, value, display): - self.value = value - self.display = display - - def __iter__(self): - return (x for x in [self.value, self.display]) - - def __len__(self): - return 2 - - -class Things(object): - - def __iter__(self): - return (x for x in [ThingItem(1, 2), ThingItem(3, 4)]) - - -class ThingWithIterableChoices(models.Model): - - # Testing choices= Iterable of Iterables - # See: https://code.djangoproject.com/ticket/20430 - thing = models.CharField(max_length=100, blank=True, choices=Things()) - - class Meta: - # Models created as unmanaged as these aren't ever queried - managed = False - - -class ManyToManyRel(models.Model): - thing1 = models.ManyToManyField(ThingWithIterableChoices, related_name='+') - thing2 = models.ManyToManyField(ThingWithIterableChoices, related_name='+') - - class Meta: - # Models created as unmanaged as these aren't ever queried - managed = False - - -class FKRel(models.Model): - thing1 = models.ForeignKey(ThingWithIterableChoices, models.CASCADE, related_name='+') - thing2 = models.ForeignKey(ThingWithIterableChoices, models.CASCADE, related_name='+') - - class Meta: - # Models created as unmanaged as these aren't ever queried - managed = False diff --git a/tests/model_validation/tests.py b/tests/model_validation/tests.py deleted file mode 100644 index 0e137af5f2..0000000000 --- a/tests/model_validation/tests.py +++ /dev/null @@ -1,98 +0,0 @@ -from django.core import management -from django.core.checks import Error -from django.core.checks.model_checks import _check_lazy_references -from django.db import models -from django.db.models.signals import post_init -from django.test import SimpleTestCase -from django.test.utils import isolate_apps, override_settings -from django.utils import six - - -@override_settings( - INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes'], - SILENCED_SYSTEM_CHECKS=['fields.W342'], # ForeignKey(unique=True) -) -class ModelValidationTest(SimpleTestCase): - def test_models_validate(self): - # All our models should validate properly - # Validation Tests: - # * choices= Iterable of Iterables - # See: https://code.djangoproject.com/ticket/20430 - # * related_name='+' doesn't clash with another '+' - # See: https://code.djangoproject.com/ticket/21375 - management.call_command("check", stdout=six.StringIO()) - - @isolate_apps('django.contrib.auth', kwarg_name='apps') - def test_lazy_reference_checks(self, apps): - - class DummyModel(models.Model): - author = models.ForeignKey('Author', models.CASCADE) - - class Meta: - app_label = "model_validation" - - class DummyClass(object): - def __call__(self, **kwargs): - pass - - def dummy_method(self): - pass - - def dummy_function(*args, **kwargs): - pass - - apps.lazy_model_operation(dummy_function, ('auth', 'imaginarymodel')) - apps.lazy_model_operation(dummy_function, ('fanciful_app', 'imaginarymodel')) - - post_init.connect(dummy_function, sender='missing-app.Model', apps=apps) - post_init.connect(DummyClass(), sender='missing-app.Model', apps=apps) - post_init.connect(DummyClass().dummy_method, sender='missing-app.Model', apps=apps) - - errors = _check_lazy_references(apps) - expected = [ - Error( - "%r contains a lazy reference to auth.imaginarymodel, " - "but app 'auth' doesn't provide model 'imaginarymodel'." % dummy_function, - obj=dummy_function, - id='models.E022', - ), - Error( - "%r contains a lazy reference to fanciful_app.imaginarymodel, " - "but app 'fanciful_app' isn't installed." % dummy_function, - obj=dummy_function, - id='models.E022', - ), - Error( - "An instance of class 'DummyClass' was connected to " - "the 'post_init' signal with a lazy reference to the sender " - "'missing-app.model', but app 'missing-app' isn't installed.", - hint=None, - obj='model_validation.tests', - id='signals.E001', - ), - Error( - "Bound method 'DummyClass.dummy_method' was connected to the " - "'post_init' signal with a lazy reference to the sender " - "'missing-app.model', but app 'missing-app' isn't installed.", - hint=None, - obj='model_validation.tests', - id='signals.E001', - ), - Error( - "The field model_validation.DummyModel.author was declared " - "with a lazy reference to 'model_validation.author', but app " - "'model_validation' isn't installed.", - hint=None, - obj=DummyModel.author.field, - id='fields.E307', - ), - Error( - "The function 'dummy_function' was connected to the 'post_init' " - "signal with a lazy reference to the sender " - "'missing-app.model', but app 'missing-app' isn't installed.", - hint=None, - obj='model_validation.tests', - id='signals.E001', - ), - ] - self.assertEqual(errors, expected)