summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2012-09-07 14:39:22 -0400
committerAndrew Godwin <andrew@aeracode.org>2012-09-07 14:39:22 -0400
commit375178fc19c1170fe046ad26befeba02fc19548c (patch)
tree9bddf460365941a53c363c4360c779ff7109645a /django
parenta92bae0f0622fb45afb94bf5448b49bc32ebb643 (diff)
Add M2M repointing
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/schema.py32
-rw-r--r--django/db/backends/sqlite3/schema.py29
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)