diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2012-09-07 14:39:22 -0400 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2012-09-07 14:39:22 -0400 |
| commit | 375178fc19c1170fe046ad26befeba02fc19548c (patch) | |
| tree | 9bddf460365941a53c363c4360c779ff7109645a /django | |
| parent | a92bae0f0622fb45afb94bf5448b49bc32ebb643 (diff) | |
Add M2M repointing
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/backends/schema.py | 32 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/schema.py | 29 |
2 files changed, 47 insertions, 14 deletions
diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index 55d687a7f6..bd86d52e88 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -21,7 +21,6 @@ class BaseDatabaseSchemaEditor(object): commit() is called. TODO: - - Repointing of M2Ms - Check constraints (PosIntField) """ @@ -154,13 +153,13 @@ class BaseDatabaseSchemaEditor(object): # Actions - def create_model(self, model): + def create_model(self, model, force=False): """ Takes a model and creates a table for it in the database. Will also create any accompanying indexes or unique constraints. """ # Do nothing if this is an unmanaged or proxy model - if not model._meta.managed or model._meta.proxy: + if not force and (not model._meta.managed or model._meta.proxy): return # Create column SQL, add FK deferreds if needed column_sqls = [] @@ -214,13 +213,16 @@ class BaseDatabaseSchemaEditor(object): "definition": ", ".join(column_sqls) } self.execute(sql, params) + # Make M2M tables + for field in model._meta.local_many_to_many: + self.create_model(field.rel.through, force=True) - def delete_model(self, model): + def delete_model(self, model, force=False): """ Deletes a model from the database. """ # Do nothing if this is an unmanaged or proxy model - if not model._meta.managed or model._meta.proxy: + if not force and (not model._meta.managed or model._meta.proxy): return # Delete the table self.execute(self.sql_delete_table % { @@ -287,7 +289,7 @@ class BaseDatabaseSchemaEditor(object): """ # Special-case implicit M2M tables if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: - return self.create_model(field.rel.through) + return self.create_model(field.rel.through, force=True) # Get the column's definition definition, params = self.column_sql(model, field, include_default=True) # It might not actually have a column behind it @@ -358,11 +360,10 @@ class BaseDatabaseSchemaEditor(object): # Ensure this field is even column-based old_type = old_field.db_type(connection=self.connection) new_type = self._type_for_alter(new_field) - if old_type is None and new_type is None: - # TODO: Handle M2M fields being repointed - return + if old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and old_field.rel.through._meta.auto_created and new_field.rel.through._meta.auto_created): + return self._alter_many_to_many(model, old_field, new_field, strict) elif old_type is None or new_type is None: - raise ValueError("Cannot alter field %s into %s - they are not compatible types" % ( + raise ValueError("Cannot alter field %s into %s - they are not compatible types (probably means only one is an M2M with implicit through model)" % ( old_field, new_field, )) @@ -543,6 +544,17 @@ class BaseDatabaseSchemaEditor(object): } ) + def _alter_many_to_many(self, model, old_field, new_field, strict): + "Alters M2Ms to repoint their to= endpoints." + # Rename the through table + self.alter_db_table(old_field.rel.through, old_field.rel.through._meta.db_table, new_field.rel.through._meta.db_table) + # Repoint the FK to the other side + self.alter_field( + new_field.rel.through, + old_field.rel.through._meta.get_field_by_name(old_field.m2m_reverse_field_name())[0], + new_field.rel.through._meta.get_field_by_name(new_field.m2m_reverse_field_name())[0], + ) + def _type_for_alter(self, field): """ Returns a field's type suitable for ALTER COLUMN. diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index 7938ad79cf..e660f26c87 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -101,11 +101,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): # Ensure this field is even column-based old_type = old_field.db_type(connection=self.connection) new_type = self._type_for_alter(new_field) - if old_type is None and new_type is None: - # TODO: Handle M2M fields being repointed - return + if old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and old_field.rel.through._meta.auto_created and new_field.rel.through._meta.auto_created): + return self._alter_many_to_many(model, old_field, new_field, strict) elif old_type is None or new_type is None: - raise ValueError("Cannot alter field %s into %s - they are not compatible types" % ( + raise ValueError("Cannot alter field %s into %s - they are not compatible types (probably means only one is an M2M with implicit through model)" % ( old_field, new_field, )) @@ -114,3 +113,25 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): def alter_unique_together(self, model, old_unique_together, new_unique_together): self._remake_table(model, override_uniques=new_unique_together) + + def _alter_many_to_many(self, model, old_field, new_field, strict): + "Alters M2Ms to repoint their to= endpoints." + # Make a new through table + self.create_model(new_field.rel.through) + # Copy the data across + self.execute("INSERT INTO %s (%s) SELECT %s FROM %s;" % ( + self.quote_name(new_field.rel.through._meta.db_table), + ', '.join([ + "id", + new_field.m2m_column_name(), + new_field.m2m_reverse_name(), + ]), + ', '.join([ + "id", + old_field.m2m_column_name(), + old_field.m2m_reverse_name(), + ]), + self.quote_name(old_field.rel.through._meta.db_table), + )) + # Delete the old through table + self.delete_model(old_field.rel.through, force=True) |
