Loading django/db/models/fields/related.py +31 −0 Original line number Diff line number Diff line Loading @@ -1194,6 +1194,7 @@ class ManyToManyField(RelatedField): errors.extend(self._check_unique(**kwargs)) errors.extend(self._check_relationship_model(**kwargs)) errors.extend(self._check_ignored_options(**kwargs)) errors.extend(self._check_table_uniqueness(**kwargs)) return errors def _check_unique(self, **kwargs): Loading Loading @@ -1429,6 +1430,36 @@ class ManyToManyField(RelatedField): return errors def _check_table_uniqueness(self, **kwargs): if isinstance(self.remote_field.through, six.string_types): return [] registered_tables = { model._meta.db_table: model for model in self.opts.apps.get_models(include_auto_created=True) if model != self.remote_field.through } m2m_db_table = self.m2m_db_table() if m2m_db_table in registered_tables: model = registered_tables[m2m_db_table] if model._meta.auto_created: def _get_field_name(model): for field in model._meta.auto_created._meta.many_to_many: if field.remote_field.through is model: return field.name opts = model._meta.auto_created._meta clashing_obj = '%s.%s' % (opts.label, _get_field_name(model)) else: clashing_obj = '%s' % model._meta.label return [ checks.Error( "The field's intermediary table '%s' clashes with the " "table name of '%s'." % (m2m_db_table, clashing_obj), obj=self, id='fields.E340', ) ] return [] def deconstruct(self): name, path, args, kwargs = super(ManyToManyField, self).deconstruct() # Handle the simpler arguments. Loading docs/ref/checks.txt +2 −0 Original line number Diff line number Diff line Loading @@ -245,6 +245,8 @@ Related Fields * **fields.E338**: The intermediary model ``<through model>`` has no field ``<field name>``. * **fields.E339**: ``<model>.<field name>`` is not a foreign key to ``<model>``. * **fields.E340**: The field's intermediary table ``<table name>`` clashes with the table name of ``<model>``/``<model>.<field name>``. * **fields.W340**: ``null`` has no effect on ``ManyToManyField``. * **fields.W341**: ``ManyToManyField`` does not support ``validators``. * **fields.W342**: Setting ``unique=True`` on a ``ForeignKey`` has the same Loading tests/invalid_models_tests/test_models.py +66 −0 Original line number Diff line number Diff line Loading @@ -761,3 +761,69 @@ class OtherModelTests(SimpleTestCase): 'as an implicit link is deprecated.' ) self.assertEqual(ParkingLot._meta.pk.name, 'parent') def test_m2m_table_name_clash(self): class Foo(models.Model): bar = models.ManyToManyField('Bar', db_table='myapp_bar') class Meta: db_table = 'myapp_foo' class Bar(models.Model): class Meta: db_table = 'myapp_bar' self.assertEqual(Foo.check(), [ Error( "The field's intermediary table 'myapp_bar' clashes with the " "table name of 'invalid_models_tests.Bar'.", obj=Foo._meta.get_field('bar'), id='fields.E340', ) ]) def test_m2m_field_table_name_clash(self): class Foo(models.Model): pass class Bar(models.Model): foos = models.ManyToManyField(Foo, db_table='clash') class Baz(models.Model): foos = models.ManyToManyField(Foo, db_table='clash') self.assertEqual(Bar.check() + Baz.check(), [ Error( "The field's intermediary table 'clash' clashes with the " "table name of 'invalid_models_tests.Baz.foos'.", obj=Bar._meta.get_field('foos'), id='fields.E340', ), Error( "The field's intermediary table 'clash' clashes with the " "table name of 'invalid_models_tests.Bar.foos'.", obj=Baz._meta.get_field('foos'), id='fields.E340', ) ]) def test_m2m_autogenerated_table_name_clash(self): class Foo(models.Model): class Meta: db_table = 'bar_foos' class Bar(models.Model): # The autogenerated `db_table` will be bar_foos. foos = models.ManyToManyField(Foo) class Meta: db_table = 'bar' self.assertEqual(Bar.check(), [ Error( "The field's intermediary table 'bar_foos' clashes with the " "table name of 'invalid_models_tests.Foo'.", obj=Bar._meta.get_field('foos'), id='fields.E340', ) ]) Loading
django/db/models/fields/related.py +31 −0 Original line number Diff line number Diff line Loading @@ -1194,6 +1194,7 @@ class ManyToManyField(RelatedField): errors.extend(self._check_unique(**kwargs)) errors.extend(self._check_relationship_model(**kwargs)) errors.extend(self._check_ignored_options(**kwargs)) errors.extend(self._check_table_uniqueness(**kwargs)) return errors def _check_unique(self, **kwargs): Loading Loading @@ -1429,6 +1430,36 @@ class ManyToManyField(RelatedField): return errors def _check_table_uniqueness(self, **kwargs): if isinstance(self.remote_field.through, six.string_types): return [] registered_tables = { model._meta.db_table: model for model in self.opts.apps.get_models(include_auto_created=True) if model != self.remote_field.through } m2m_db_table = self.m2m_db_table() if m2m_db_table in registered_tables: model = registered_tables[m2m_db_table] if model._meta.auto_created: def _get_field_name(model): for field in model._meta.auto_created._meta.many_to_many: if field.remote_field.through is model: return field.name opts = model._meta.auto_created._meta clashing_obj = '%s.%s' % (opts.label, _get_field_name(model)) else: clashing_obj = '%s' % model._meta.label return [ checks.Error( "The field's intermediary table '%s' clashes with the " "table name of '%s'." % (m2m_db_table, clashing_obj), obj=self, id='fields.E340', ) ] return [] def deconstruct(self): name, path, args, kwargs = super(ManyToManyField, self).deconstruct() # Handle the simpler arguments. Loading
docs/ref/checks.txt +2 −0 Original line number Diff line number Diff line Loading @@ -245,6 +245,8 @@ Related Fields * **fields.E338**: The intermediary model ``<through model>`` has no field ``<field name>``. * **fields.E339**: ``<model>.<field name>`` is not a foreign key to ``<model>``. * **fields.E340**: The field's intermediary table ``<table name>`` clashes with the table name of ``<model>``/``<model>.<field name>``. * **fields.W340**: ``null`` has no effect on ``ManyToManyField``. * **fields.W341**: ``ManyToManyField`` does not support ``validators``. * **fields.W342**: Setting ``unique=True`` on a ``ForeignKey`` has the same Loading
tests/invalid_models_tests/test_models.py +66 −0 Original line number Diff line number Diff line Loading @@ -761,3 +761,69 @@ class OtherModelTests(SimpleTestCase): 'as an implicit link is deprecated.' ) self.assertEqual(ParkingLot._meta.pk.name, 'parent') def test_m2m_table_name_clash(self): class Foo(models.Model): bar = models.ManyToManyField('Bar', db_table='myapp_bar') class Meta: db_table = 'myapp_foo' class Bar(models.Model): class Meta: db_table = 'myapp_bar' self.assertEqual(Foo.check(), [ Error( "The field's intermediary table 'myapp_bar' clashes with the " "table name of 'invalid_models_tests.Bar'.", obj=Foo._meta.get_field('bar'), id='fields.E340', ) ]) def test_m2m_field_table_name_clash(self): class Foo(models.Model): pass class Bar(models.Model): foos = models.ManyToManyField(Foo, db_table='clash') class Baz(models.Model): foos = models.ManyToManyField(Foo, db_table='clash') self.assertEqual(Bar.check() + Baz.check(), [ Error( "The field's intermediary table 'clash' clashes with the " "table name of 'invalid_models_tests.Baz.foos'.", obj=Bar._meta.get_field('foos'), id='fields.E340', ), Error( "The field's intermediary table 'clash' clashes with the " "table name of 'invalid_models_tests.Bar.foos'.", obj=Baz._meta.get_field('foos'), id='fields.E340', ) ]) def test_m2m_autogenerated_table_name_clash(self): class Foo(models.Model): class Meta: db_table = 'bar_foos' class Bar(models.Model): # The autogenerated `db_table` will be bar_foos. foos = models.ManyToManyField(Foo) class Meta: db_table = 'bar' self.assertEqual(Bar.check(), [ Error( "The field's intermediary table 'bar_foos' clashes with the " "table name of 'invalid_models_tests.Foo'.", obj=Bar._meta.get_field('foos'), id='fields.E340', ) ])