1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #12810 -- Added a check for clashing ManyToManyField.db_table names.

This commit is contained in:
Berker Peksag 2016-06-03 12:55:30 -07:00 committed by Tim Graham
parent faeeb84edf
commit 0bce2f102c
3 changed files with 99 additions and 0 deletions

View File

@ -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):
@ -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.

View File

@ -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

View File

@ -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',
)
])