+++ /dev/null
-
-from django.core.management.color import no_style
-from django.db import connection, transaction, models
-from django.db.backends.util import truncate_name
-from django.dispatch import dispatcher
-from django.conf import settings
-
-class DatabaseOperations(object):
-
- """
- Generic SQL implementation of the DatabaseOperations.
- Some of this code comes from Django Evolution.
- """
-
- def __init__(self):
- self.debug = False
- self.deferred_sql = []
-
-
- def execute(self, sql, params=[]):
- """
- Executes the given SQL statement, with optional parameters.
- If the instance's debug attribute is True, prints out what it executes.
- """
- cursor = connection.cursor()
- if self.debug:
- print " = %s" % sql, params
- cursor.execute(sql, params)
- try:
- return cursor.fetchall()
- except:
- return []
-
-
- def add_deferred_sql(self, sql):
- """
- Add a SQL statement to the deferred list, that won't be executed until
- this instance's execute_deferred_sql method is run.
- """
- self.deferred_sql.append(sql)
-
-
- def execute_deferred_sql(self):
- """
- Executes all deferred SQL, resetting the deferred_sql list
- """
- for sql in self.deferred_sql:
- self.execute(sql)
-
- self.deferred_sql = []
-
-
- def create_table(self, table_name, fields):
- """
- Creates the table 'table_name'. 'fields' is a tuple of fields,
- each repsented by a 2-part tuple of field name and a
- django.db.models.fields.Field object
- """
- qn = connection.ops.quote_name
- columns = [
- self.column_sql(table_name, field_name, field)
- for field_name, field in fields
- ]
-
- self.execute('CREATE TABLE %s (%s);' % (qn(table_name), ', '.join([col for col in columns if col])))
-
- add_table = create_table # Alias for consistency's sake
-
-
- def rename_table(self, old_table_name, table_name):
- """
- Renames the table 'old_table_name' to 'table_name'.
- """
- if old_table_name == table_name:
- # No Operation
- return
- qn = connection.ops.quote_name
- params = (qn(old_table_name), qn(table_name))
- self.execute('ALTER TABLE %s RENAME TO %s;' % params)
-
-
- def delete_table(self, table_name):
- """
- Deletes the table 'table_name'.
- """
- qn = connection.ops.quote_name
- params = (qn(table_name), )
- self.execute('DROP TABLE %s;' % params)
-
- drop_table = delete_table
-
-
- def add_column(self, table_name, name, field):
- """
- Adds the column 'name' to the table 'table_name'.
- Uses the 'field' paramater, a django.db.models.fields.Field instance,
- to generate the necessary sql
-
- @param table_name: The name of the table to add the column to
- @param name: The name of the column to add
- @param field: The field to use
- """
- qn = connection.ops.quote_name
- sql = self.column_sql(table_name, name, field)
- if sql:
- params = (
- qn(table_name),
- sql,
- )
- sql = 'ALTER TABLE %s ADD COLUMN %s;' % params
- self.execute(sql)
-
-
- alter_string_set_type = 'ALTER COLUMN %(column)s TYPE %(type)s'
- alter_string_set_null = 'ALTER COLUMN %(column)s SET NOT NULL'
- alter_string_drop_null = 'ALTER COLUMN %(column)s DROP NOT NULL'
-
- def alter_column(self, table_name, name, field):
- """
- Alters the given column name so it will match the given field.
- Note that conversion between the two by the database must be possible.
-
- @param table_name: The name of the table to add the column to
- @param name: The name of the column to alter
- @param field: The new field definition to use
- """
-
- # hook for the field to do any resolution prior to it's attributes being queried
- if hasattr(field, 'south_init'):
- field.south_init()
-
- qn = connection.ops.quote_name
-
- # First, change the type
- params = {
- "column": qn(name),
- "type": field.db_type(),
- }
- sqls = [self.alter_string_set_type % params]
-
-
- # Next, set any default
- params = (
- qn(name),
- )
-
- if not field.null and field.has_default():
- default = field.get_default()
- if isinstance(default, basestring):
- default = "'%s'" % default
- params += ("SET DEFAULT %s",)
- else:
- params += ("DROP DEFAULT",)
-
- sqls.append('ALTER COLUMN %s %s ' % params)
-
-
- # Next, nullity
- params = {
- "column": qn(name),
- "type": field.db_type(),
- }
- if field.null:
- sqls.append(self.alter_string_drop_null % params)
- else:
- sqls.append(self.alter_string_set_null % params)
-
-
- # TODO: Unique
-
- self.execute("ALTER TABLE %s %s;" % (qn(table_name), ", ".join(sqls)))
-
-
- def column_sql(self, table_name, field_name, field, tablespace=''):
- """
- Creates the SQL snippet for a column. Used by add_column and add_table.
- """
- qn = connection.ops.quote_name
-
- field.set_attributes_from_name(field_name)
-
- # hook for the field to do any resolution prior to it's attributes being queried
- if hasattr(field, 'south_init'):
- field.south_init()
-
- sql = field.db_type()
- if sql:
- field_output = [qn(field.column), sql]
- field_output.append('%sNULL' % (not field.null and 'NOT ' or ''))
- if field.primary_key:
- field_output.append('PRIMARY KEY')
- elif field.unique:
- field_output.append('UNIQUE')
-
- tablespace = field.db_tablespace or tablespace
- if tablespace and connection.features.supports_tablespaces and field.unique:
- # We must specify the index tablespace inline, because we
- # won't be generating a CREATE INDEX statement for this field.
- field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
-
- sql = ' '.join(field_output)
- sqlparams = ()
- # if the field is "NOT NULL" and a default value is provided, create the column with it
- # this allows the addition of a NOT NULL field to a table with existing rows
- if not field.null and field.has_default():
- default = field.get_default()
- if isinstance(default, basestring):
- default = "'%s'" % default.replace("'", "''")
- sql += " DEFAULT %s"
- sqlparams = (default)
-
- if field.rel:
- self.add_deferred_sql(
- self.foreign_key_sql(
- table_name,
- field.column,
- field.rel.to._meta.db_table,
- field.rel.to._meta.get_field(field.rel.field_name).column
- )
- )
-
- if field.db_index and not field.unique:
- self.add_deferred_sql(self.create_index_sql(table_name, [field.column]))
-
- if hasattr(field, 'post_create_sql'):
- style = no_style()
- for stmt in field.post_create_sql(style, table_name):
- self.add_deferred_sql(stmt)
-
- if sql:
- return sql % sqlparams
- else:
- return None
-
- def foreign_key_sql(self, from_table_name, from_column_name, to_table_name, to_column_name):
- """
- Generates a full SQL statement to add a foreign key constraint
- """
- constraint_name = '%s_refs_%s_%x' % (from_column_name, to_column_name, abs(hash((from_table_name, to_table_name))))
- return 'ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % (
- from_table_name,
- truncate_name(constraint_name, connection.ops.max_name_length()),
- from_column_name,
- to_table_name,
- to_column_name,
- connection.ops.deferrable_sql() # Django knows this
- )
-
- def create_index_name(self, table_name, column_names):
- """
- Generate a unique name for the index
- """
- index_unique_name = ''
- if len(column_names) > 1:
- index_unique_name = '_%x' % abs(hash((table_name, ','.join(column_names))))
-
- return '%s_%s%s' % (table_name, column_names[0], index_unique_name)
-
- def create_index_sql(self, table_name, column_names, unique=False, db_tablespace=''):
- """
- Generates a create index statement on 'table_name' for a list of 'column_names'
- """
- if not column_names:
- print "No column names supplied on which to create an index"
- return ''
-
- if db_tablespace and connection.features.supports_tablespaces:
- tablespace_sql = ' ' + connection.ops.tablespace_sql(db_tablespace)
- else:
- tablespace_sql = ''
-
- index_name = self.create_index_name(table_name, column_names)
- qn = connection.ops.quote_name
- return 'CREATE %sINDEX %s ON %s (%s)%s;' % (
- unique and 'UNIQUE ' or '',
- index_name,
- table_name,
- ','.join([qn(field) for field in column_names]),
- tablespace_sql
- )
-
- def create_index(self, table_name, column_names, unique=False, db_tablespace=''):
- """ Executes a create index statement """
- sql = self.create_index_sql(table_name, column_names, unique, db_tablespace)
- self.execute(sql)
-
-
- def delete_index(self, table_name, column_names, db_tablespace=''):
- """
- Deletes an index created with create_index.
- This is possible using only columns due to the deterministic
- index naming function which relies on column names.
- """
- name = self.create_index_name(table_name, column_names)
- sql = "DROP INDEX %s" % name
- self.execute(sql)
-
-
- def delete_column(self, table_name, name):
- """
- Deletes the column 'column_name' from the table 'table_name'.
- """
- qn = connection.ops.quote_name
- params = (qn(table_name), qn(name))
- self.execute('ALTER TABLE %s DROP COLUMN %s CASCADE;' % params, [])
-
-
- def rename_column(self, table_name, old, new):
- """
- Renames the column 'old' from the table 'table_name' to 'new'.
- """
- raise NotImplementedError("rename_column has no generic SQL syntax")
-
-
- def start_transaction(self):
- """
- Makes sure the following commands are inside a transaction.
- Must be followed by a (commit|rollback)_transaction call.
- """
- transaction.commit_unless_managed()
- transaction.enter_transaction_management()
- transaction.managed(True)
-
-
- def commit_transaction(self):
- """
- Commits the current transaction.
- Must be preceded by a start_transaction call.
- """
- transaction.commit()
- transaction.leave_transaction_management()
-
-
- def rollback_transaction(self):
- """
- Rolls back the current transaction.
- Must be preceded by a start_transaction call.
- """
- transaction.rollback()
- transaction.leave_transaction_management()
-
-
- def send_create_signal(self, app_label, model_names):
- """
- Sends a post_syncdb signal for the model specified.
-
- If the model is not found (perhaps it's been deleted?),
- no signal is sent.
-
- TODO: The behavior of django.contrib.* apps seems flawed in that
- they don't respect created_models. Rather, they blindly execute
- over all models within the app sending the signal. This is a
- patch we should push Django to make For now, this should work.
- """
- app = models.get_app(app_label)
- if not app:
- return
-
- created_models = []
- for model_name in model_names:
- model = models.get_model(app_label, model_name)
- if model:
- created_models.append(model)
-
- if created_models:
- # syncdb defaults -- perhaps take these as options?
- verbosity = 1
- interactive = True
-
- if hasattr(dispatcher, "send"):
- dispatcher.send(signal=models.signals.post_syncdb, sender=app,
- app=app, created_models=created_models,
- verbosity=verbosity, interactive=interactive)
- else:
- models.signals.post_syncdb.send(sender=app,
- app=app, created_models=created_models,
- verbosity=verbosity, interactive=interactive)
-
- def mock_model(self, model_name, db_table, db_tablespace='',
- pk_field_name='id', pk_field_type=models.AutoField,
- pk_field_kwargs={}):
- """
- Generates a MockModel class that provides enough information
- to be used by a foreign key/many-to-many relationship.
-
- Migrations should prefer to use these rather than actual models
- as models could get deleted over time, but these can remain in
- migration files forever.
- """
- class MockOptions(object):
- def __init__(self):
- self.db_table = db_table
- self.db_tablespace = db_tablespace or settings.DEFAULT_TABLESPACE
- self.object_name = model_name
- self.module_name = model_name.lower()
-
- if pk_field_type == models.AutoField:
- pk_field_kwargs['primary_key'] = True
-
- self.pk = pk_field_type(**pk_field_kwargs)
- self.pk.set_attributes_from_name(pk_field_name)
- self.abstract = False
-
- def get_field_by_name(self, field_name):
- # we only care about the pk field
- return (self.pk, self.model, True, False)
-
- def get_field(self, name):
- # we only care about the pk field
- return self.pk
-
- class MockModel(object):
- _meta = None
-
- # We need to return an actual class object here, not an instance
- MockModel._meta = MockOptions()
- MockModel._meta.model = MockModel
- return MockModel