mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #12810 -- Added a check for clashing ManyToManyField.db_table names.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						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', | ||||
|             ) | ||||
|         ]) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user