mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[1.8.x] Fixed #24104 -- Fixed check to look on field.many_to_many instead of class instance
Backport of 38c17871bb from master
			
			
This commit is contained in:
		
				
					committed by
					
						 Markus Holtermann
						Markus Holtermann
					
				
			
			
				
	
			
			
			
						parent
						
							0580133971
						
					
				
				
					commit
					11a5e45b96
				
			| @@ -1,7 +1,6 @@ | ||||
| import hashlib | ||||
|  | ||||
| from django.db.backends.utils import truncate_name | ||||
| from django.db.models.fields.related import ManyToManyField | ||||
| from django.db.transaction import atomic | ||||
| from django.utils import six | ||||
| from django.utils.encoding import force_bytes | ||||
| @@ -380,7 +379,7 @@ class BaseDatabaseSchemaEditor(object): | ||||
|         table instead (for M2M fields) | ||||
|         """ | ||||
|         # Special-case implicit M2M tables | ||||
|         if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: | ||||
|         if field.many_to_many and field.rel.through._meta.auto_created: | ||||
|             return self.create_model(field.rel.through) | ||||
|         # Get the column's definition | ||||
|         definition, params = self.column_sql(model, field, include_default=True) | ||||
| @@ -424,7 +423,7 @@ class BaseDatabaseSchemaEditor(object): | ||||
|         but for M2Ms may involve deleting a table. | ||||
|         """ | ||||
|         # Special-case implicit M2M tables | ||||
|         if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: | ||||
|         if field.many_to_many and field.rel.through._meta.auto_created: | ||||
|             return self.delete_model(field.rel.through) | ||||
|         # It might not actually have a column behind it | ||||
|         if field.db_parameters(connection=self.connection)['type'] is None: | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from decimal import Decimal | ||||
|  | ||||
| from django.apps.registry import Apps | ||||
| from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||
| from django.db.models.fields.related import ManyToManyField | ||||
| from django.utils import six | ||||
|  | ||||
| import _sqlite3 | ||||
| @@ -71,7 +70,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|         for field in create_fields: | ||||
|             body[field.name] = field | ||||
|             # Choose a default and insert it into the copy map | ||||
|             if not isinstance(field, ManyToManyField): | ||||
|             if not field.many_to_many: | ||||
|                 mapping[field.column] = self.quote_value( | ||||
|                     self.effective_default(field) | ||||
|                 ) | ||||
| @@ -94,7 +93,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|             del body[field.name] | ||||
|             del mapping[field.column] | ||||
|             # Remove any implicit M2M tables | ||||
|             if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: | ||||
|             if field.many_to_many and field.rel.through._meta.auto_created: | ||||
|                 return self.delete_model(field.rel.through) | ||||
|         # Work inside a new app registry | ||||
|         apps = Apps() | ||||
| @@ -173,7 +172,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|         table instead (for M2M fields) | ||||
|         """ | ||||
|         # Special-case implicit M2M tables | ||||
|         if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: | ||||
|         if field.many_to_many and field.rel.through._meta.auto_created: | ||||
|             return self.create_model(field.rel.through) | ||||
|         self._remake_table(model, create_fields=[field]) | ||||
|  | ||||
| @@ -183,7 +182,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): | ||||
|         but for M2Ms may involve deleting a table. | ||||
|         """ | ||||
|         # M2M fields are a special case | ||||
|         if isinstance(field, ManyToManyField): | ||||
|         if field.many_to_many: | ||||
|             # For implicit M2M tables, delete the auto-created table | ||||
|             if field.rel.through._meta.auto_created: | ||||
|                 self.delete_model(field.rel.through) | ||||
|   | ||||
							
								
								
									
										54
									
								
								tests/schema/fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								tests/schema/fields.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| from django.db.models.fields.related import ( | ||||
|     create_many_to_many_intermediary_model, | ||||
|     ManyToManyField, ManyToManyRel, RelatedField, | ||||
|     RECURSIVE_RELATIONSHIP_CONSTANT, ReverseManyRelatedObjectsDescriptor, | ||||
| ) | ||||
|  | ||||
| from django.utils.functional import curry | ||||
|  | ||||
|  | ||||
| class CustomManyToManyField(RelatedField): | ||||
|     """ | ||||
|     Ticket #24104 - Need to have a custom ManyToManyField, | ||||
|     which is not an inheritor of ManyToManyField. | ||||
|     """ | ||||
|     many_to_many = True | ||||
|  | ||||
|     def __init__(self, to, db_constraint=True, swappable=True, **kwargs): | ||||
|         try: | ||||
|             to._meta | ||||
|         except AttributeError: | ||||
|             to = str(to) | ||||
|         kwargs['rel'] = ManyToManyRel( | ||||
|             self, to, | ||||
|             related_name=kwargs.pop('related_name', None), | ||||
|             related_query_name=kwargs.pop('related_query_name', None), | ||||
|             limit_choices_to=kwargs.pop('limit_choices_to', None), | ||||
|             symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT), | ||||
|             through=kwargs.pop('through', None), | ||||
|             through_fields=kwargs.pop('through_fields', None), | ||||
|             db_constraint=db_constraint, | ||||
|         ) | ||||
|         self.swappable = swappable | ||||
|         self.db_table = kwargs.pop('db_table', None) | ||||
|         if kwargs['rel'].through is not None: | ||||
|             assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." | ||||
|         super(CustomManyToManyField, self).__init__(**kwargs) | ||||
|  | ||||
|     def contribute_to_class(self, cls, name, **kwargs): | ||||
|         if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): | ||||
|             self.rel.related_name = "%s_rel_+" % name | ||||
|         super(CustomManyToManyField, self).contribute_to_class(cls, name, **kwargs) | ||||
|         if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped: | ||||
|             self.rel.through = create_many_to_many_intermediary_model(self, cls) | ||||
|         setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) | ||||
|         self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) | ||||
|  | ||||
|     def get_internal_type(self): | ||||
|         return 'ManyToManyField' | ||||
|  | ||||
|     # Copy those methods from ManyToManyField because they don't call super() internally | ||||
|     contribute_to_related_class = ManyToManyField.__dict__['contribute_to_related_class'] | ||||
|     _get_m2m_attr = ManyToManyField.__dict__['_get_m2m_attr'] | ||||
|     _get_m2m_reverse_attr = ManyToManyField.__dict__['_get_m2m_reverse_attr'] | ||||
|     _get_m2m_db_table = ManyToManyField.__dict__['_get_m2m_db_table'] | ||||
| @@ -7,6 +7,7 @@ from django.db.models.fields import (BinaryField, BooleanField, CharField, Integ | ||||
|     PositiveIntegerField, SlugField, TextField) | ||||
| from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField | ||||
| from django.db.transaction import atomic | ||||
| from .fields import CustomManyToManyField | ||||
| from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, | ||||
|     BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, | ||||
|     UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, | ||||
| @@ -1303,3 +1304,47 @@ class SchemaTests(TransactionTestCase): | ||||
|             cursor.execute("SELECT surname FROM schema_author;") | ||||
|             item = cursor.fetchall()[0] | ||||
|             self.assertEqual(item[0], None if connection.features.interprets_empty_strings_as_nulls else '') | ||||
|  | ||||
|     def test_custom_manytomanyfield(self): | ||||
|         """ | ||||
|         #24104 - Schema editors should look for many_to_many | ||||
|         """ | ||||
|         # Create the tables | ||||
|         with connection.schema_editor() as editor: | ||||
|             editor.create_model(AuthorWithM2M) | ||||
|             editor.create_model(TagM2MTest) | ||||
|         # Create an M2M field | ||||
|         new_field = CustomManyToManyField("schema.TagM2MTest", related_name="authors") | ||||
|         new_field.contribute_to_class(AuthorWithM2M, "tags") | ||||
|         # Ensure there's no m2m table there | ||||
|         self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) | ||||
|         try: | ||||
|             # Add the field | ||||
|             with connection.schema_editor() as editor: | ||||
|                 editor.add_field( | ||||
|                     AuthorWithM2M, | ||||
|                     new_field, | ||||
|                 ) | ||||
|             # Ensure there is now an m2m table there | ||||
|             columns = self.column_classes(new_field.rel.through) | ||||
|             self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") | ||||
|  | ||||
|             # "Alter" the field. This should not rename the DB table to itself. | ||||
|             with connection.schema_editor() as editor: | ||||
|                 editor.alter_field( | ||||
|                     AuthorWithM2M, | ||||
|                     new_field, | ||||
|                     new_field, | ||||
|                 ) | ||||
|  | ||||
|             # Remove the M2M table again | ||||
|             with connection.schema_editor() as editor: | ||||
|                 editor.remove_field( | ||||
|                     AuthorWithM2M, | ||||
|                     new_field, | ||||
|                 ) | ||||
|             # Ensure there's no m2m table there | ||||
|             self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) | ||||
|         finally: | ||||
|             # Cleanup model states | ||||
|             AuthorWithM2M._meta.local_many_to_many.remove(new_field) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user