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:
parent
faeeb84edf
commit
0bce2f102c
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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…
Reference in New Issue
Block a user