diff -r ebaad720f88b -r 526ebd3988b0 web/lib/django_extensions/management/commands/sqldiff.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/django_extensions/management/commands/sqldiff.py Wed Jan 20 12:37:40 2010 +0100 @@ -0,0 +1,572 @@ +""" +sqldiff.py - Prints the (approximated) difference between models and database + +TODO: + - better support for relations + - better support for constraints (mainly postgresql?) + - support for table spaces with postgresql + +KNOWN ISSUES: + - MySQL has by far the most problems with introspection. Please be + carefull when using MySQL with sqldiff. + - Booleans are reported back as Integers, so there's know way to know if + there was a real change. + - Varchar sizes are reported back without unicode support so there size + may change in comparison to the real length of the varchar. + - Some of the 'fixes' to counter these problems might create false + positives or false negatives. +""" + +from django.core.management.base import BaseCommand +from django.core.management import sql as _sql +from django.core.management import CommandError +from django.core.management.color import no_style +from django.db import transaction, connection +from django.db.models.fields import IntegerField +from optparse import make_option + +ORDERING_FIELD = IntegerField('_order', null=True) + +def flatten(l, ltypes=(list, tuple)): + ltype = type(l) + l = list(l) + i = 0 + while i < len(l): + while isinstance(l[i], ltypes): + if not l[i]: + l.pop(i) + i -= 1 + break + else: + l[i:i + 1] = l[i] + i += 1 + return ltype(l) + +class SQLDiff(object): + DATA_TYPES_REVERSE_OVERRIDE = { + } + + DIFF_TYPES = [ + 'comment', + 'table-missing-in-db', + 'field-missing-in-db', + 'field-missing-in-model', + 'index-missing-in-db', + 'index-missing-in-model', + 'unique-missing-in-db', + 'unique-missing-in-model', + 'field-type-differ', + 'field-parameter-differ', + ] + DIFF_TEXTS = { + 'comment': 'comment: %(0)s', + 'table-missing-in-db': "table '%(0)s' missing in database", + 'field-missing-in-db' : "field '%(1)s' defined in model but missing in database", + 'field-missing-in-model' : "field '%(1)s' defined in database but missing in model", + 'index-missing-in-db' : "field '%(1)s' INDEX defined in model but missing in database", + 'index-missing-in-model' : "field '%(1)s' INDEX defined in database schema but missing in model", + 'unique-missing-in-db' : "field '%(1)s' UNIQUE defined in model but missing in database", + 'unique-missing-in-model' : "field '%(1)s' UNIQUE defined in database schema but missing in model", + 'field-type-differ' : "field '%(1)s' not of same type: db='%(3)s', model='%(2)s'", + 'field-parameter-differ' : "field '%(1)s' parameters differ: db='%(3)s', model='%(2)s'", + } + + SQL_FIELD_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2])) + SQL_FIELD_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP COLUMN'), style.SQL_FIELD(qn(args[1]))) + SQL_INDEX_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('CREATE INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2]))), style.SQL_KEYWORD('ON'), style.SQL_TABLE(qn(args[0])), style.SQL_FIELD(qn(args[1]))) + # FIXME: need to lookup index name instead of just appending _idx to table + fieldname + SQL_INDEX_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s;" % (style.SQL_KEYWORD('DROP INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2])))) + SQL_UNIQUE_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_KEYWORD('UNIQUE'), style.SQL_FIELD(qn(args[1]))) + # FIXME: need to lookup unique constraint name instead of appending _key to table + fieldname + SQL_UNIQUE_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP'), style.SQL_KEYWORD('CONSTRAINT'), style.SQL_TABLE(qn("%s_key" % ('_'.join(args[:2]))))) + SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2])) + SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2])) + SQL_COMMENT = lambda self, style, qn, args: style.NOTICE('-- Comment: %s' % style.SQL_TABLE(args[0])) + SQL_TABLE_MISSING_IN_DB = lambda self, style, qn, args: style.NOTICE('-- Table missing: %s' % args[0]) + + def __init__(self, app_models, options): + self.app_models = app_models + self.options = options + self.dense = options.get('dense_output', False) + + try: + self.introspection = connection.introspection + except AttributeError: + from django.db import get_introspection_module + self.introspection = get_introspection_module() + + self.cursor = connection.cursor() + self.django_tables = self.get_django_tables(options.get('only_existing', True)) + self.db_tables = self.introspection.get_table_list(self.cursor) + self.differences = [] + self.unknown_db_fields = {} + + self.DIFF_SQL = { + 'comment': self.SQL_COMMENT, + 'table-missing-in-db': self.SQL_TABLE_MISSING_IN_DB, + 'field-missing-in-db': self.SQL_FIELD_MISSING_IN_DB, + 'field-missing-in-model': self.SQL_FIELD_MISSING_IN_MODEL, + 'index-missing-in-db': self.SQL_INDEX_MISSING_IN_DB, + 'index-missing-in-model': self.SQL_INDEX_MISSING_IN_MODEL, + 'unique-missing-in-db': self.SQL_UNIQUE_MISSING_IN_DB, + 'unique-missing-in-model': self.SQL_UNIQUE_MISSING_IN_MODEL, + 'field-type-differ': self.SQL_FIELD_TYPE_DIFFER, + 'field-parameter-differ': self.SQL_FIELD_PARAMETER_DIFFER, + } + + + def add_app_model_marker(self, app_label, model_name): + self.differences.append((app_label, model_name, [])) + + def add_difference(self, diff_type, *args): + assert diff_type in self.DIFF_TYPES, 'Unknown difference type' + self.differences[-1][-1].append((diff_type, args)) + + def get_django_tables(self, only_existing): + try: + django_tables = self.introspection.django_table_names(only_existing=only_existing) + except AttributeError: + # backwards compatibility for before introspection refactoring (r8296) + try: + django_tables = _sql.django_table_names(only_existing=only_existing) + except AttributeError: + # backwards compatibility for before svn r7568 + django_tables = _sql.django_table_list(only_existing=only_existing) + return django_tables + + def sql_to_dict(self, query,param): + """ sql_to_dict(query, param) -> list of dicts + + code from snippet at http://www.djangosnippets.org/snippets/1383/ + """ + cursor = connection.cursor() + cursor.execute(query,param) + fieldnames = [name[0] for name in cursor.description] + result = [] + for row in cursor.fetchall(): + rowset = [] + for field in zip(fieldnames, row): + rowset.append(field) + result.append(dict(rowset)) + return result + + def get_field_model_type(self, field): + return field.db_type() + + def get_field_db_type(self, description, field=None, table_name=None): + from django.db import models + # DB-API cursor.description + #(name, type_code, display_size, internal_size, precision, scale, null_ok) = description + type_code = description[1] + if type_code in self.DATA_TYPES_REVERSE_OVERRIDE: + reverse_type = self.DATA_TYPES_REVERSE_OVERRIDE[type_code] + else: + try: + try: + reverse_type = self.introspection.data_types_reverse[type_code] + except AttributeError: + # backwards compatibility for before introspection refactoring (r8296) + reverse_type = self.introspection.DATA_TYPES_REVERSE.get(type_code) + except KeyError: + # type_code not found in data_types_reverse map + key = (self.differences[-1][:2], description[:2]) + if key not in self.unknown_db_fields: + self.unknown_db_fields[key] = 1 + self.add_difference('comment', "Unknown database type for field '%s' (%s)" % (description[0], type_code)) + return None + + kwargs = {} + if isinstance(reverse_type, tuple): + kwargs.update(reverse_type[1]) + reverse_type = reverse_type[0] + + if reverse_type == "CharField" and description[3]: + kwargs['max_length'] = description[3] + + if reverse_type == "DecimalField": + kwargs['max_digits'] = description[4] + kwargs['decimal_places'] = description[5] + + if description[6]: + kwargs['blank'] = True + if not reverse_type in ('TextField', 'CharField'): + kwargs['null'] = True + + field_db_type = getattr(models, reverse_type)(**kwargs).db_type() + return field_db_type + + def strip_parameters(self, field_type): + if field_type: + return field_type.split(" ")[0].split("(")[0] + return field_type + + def find_unique_missing_in_db(self, meta, table_indexes, table_name): + for field in meta.fields: + if field.unique: + attname = field.db_column or field.attname + if attname in table_indexes and table_indexes[attname]['unique']: + continue + self.add_difference('unique-missing-in-db', table_name, attname) + + def find_unique_missing_in_model(self, meta, table_indexes, table_name): + # TODO: Postgresql does not list unique_togethers in table_indexes + # MySQL does + fields = dict([(field.db_column or field.name, field.unique) for field in meta.fields]) + for att_name, att_opts in table_indexes.iteritems(): + if att_opts['unique'] and att_name in fields and not fields[att_name]: + if att_name in flatten(meta.unique_together): continue + self.add_difference('unique-missing-in-model', table_name, att_name) + + def find_index_missing_in_db(self, meta, table_indexes, table_name): + for field in meta.fields: + if field.db_index: + attname = field.db_column or field.attname + if not attname in table_indexes: + self.add_difference('index-missing-in-db', table_name, attname) + + def find_index_missing_in_model(self, meta, table_indexes, table_name): + fields = dict([(field.name, field) for field in meta.fields]) + for att_name, att_opts in table_indexes.iteritems(): + if att_name in fields: + field = fields[att_name] + if field.db_index: continue + if att_opts['primary_key'] and field.primary_key: continue + if att_opts['unique'] and field.unique: continue + if att_opts['unique'] and att_name in flatten(meta.unique_together): continue + self.add_difference('index-missing-in-model', table_name, att_name) + + def find_field_missing_in_model(self, fieldmap, table_description, table_name): + for row in table_description: + if row[0] not in fieldmap: + self.add_difference('field-missing-in-model', table_name, row[0]) + + def find_field_missing_in_db(self, fieldmap, table_description, table_name): + db_fields = [row[0] for row in table_description] + for field_name, field in fieldmap.iteritems(): + if field_name not in db_fields: + self.add_difference('field-missing-in-db', table_name, field_name, field.db_type()) + + def find_field_type_differ(self, meta, table_description, table_name, func=None): + db_fields = dict([(row[0], row) for row in table_description]) + for field in meta.fields: + if field.name not in db_fields: continue + description = db_fields[field.name] + + model_type = self.strip_parameters(self.get_field_model_type(field)) + db_type = self.strip_parameters(self.get_field_db_type(description, field)) + + # use callback function if defined + if func: + model_type, db_type = func(field, description, model_type, db_type) + + if not model_type==db_type: + self.add_difference('field-type-differ', table_name, field.name, model_type, db_type) + + def find_field_parameter_differ(self, meta, table_description, table_name, func=None): + db_fields = dict([(row[0], row) for row in table_description]) + for field in meta.fields: + if field.name not in db_fields: continue + description = db_fields[field.name] + + model_type = self.get_field_model_type(field) + db_type = self.get_field_db_type(description, field, table_name) + + if not self.strip_parameters(model_type)==self.strip_parameters(db_type): + continue + + # use callback function if defined + if func: + model_type, db_type = func(field, description, model_type, db_type) + + if not model_type==db_type: + self.add_difference('field-parameter-differ', table_name, field.name, model_type, db_type) + + @transaction.commit_manually + def find_differences(self): + cur_app_label = None + for app_model in self.app_models: + meta = app_model._meta + table_name = meta.db_table + app_label = meta.app_label + + if cur_app_label!=app_label: + # Marker indicating start of difference scan for this table_name + self.add_app_model_marker(app_label, app_model.__name__) + + #if not table_name in self.django_tables: + if not table_name in self.db_tables: + # Table is missing from database + self.add_difference('table-missing-in-db', table_name) + continue + + table_indexes = self.introspection.get_indexes(self.cursor, table_name) + fieldmap = dict([(field.db_column or field.get_attname(), field) for field in meta.fields]) + + # add ordering field if model uses order_with_respect_to + if meta.order_with_respect_to: + fieldmap['_order'] = ORDERING_FIELD + + try: + table_description = self.introspection.get_table_description(self.cursor, table_name) + except Exception, e: + model_diffs.append((app_model.__name__, [str(e).strip()])) + transaction.rollback() # reset transaction + continue + + # Fields which are defined in database but not in model + # 1) find: 'unique-missing-in-model' + self.find_unique_missing_in_model(meta, table_indexes, table_name) + # 2) find: 'index-missing-in-model' + self.find_index_missing_in_model(meta, table_indexes, table_name) + # 3) find: 'field-missing-in-model' + self.find_field_missing_in_model(fieldmap, table_description, table_name) + + # Fields which are defined in models but not in database + # 4) find: 'field-missing-in-db' + self.find_field_missing_in_db(fieldmap, table_description, table_name) + # 5) find: 'unique-missing-in-db' + self.find_unique_missing_in_db(meta, table_indexes, table_name) + # 6) find: 'index-missing-in-db' + self.find_index_missing_in_db(meta, table_indexes, table_name) + + # Fields which have a different type or parameters + # 7) find: 'type-differs' + self.find_field_type_differ(meta, table_description, table_name) + # 8) find: 'type-parameter-differs' + self.find_field_parameter_differ(meta, table_description, table_name) + + def print_diff(self, style=no_style()): + """ print differences to stdout """ + if self.options.get('sql', True): + self.print_diff_sql(style) + else: + self.print_diff_text(style) + + def print_diff_text(self, style): + cur_app_label = None + for app_label, model_name, diffs in self.differences: + if not diffs: continue + if not self.dense and cur_app_label != app_label: + print style.NOTICE("+ Application:"), style.SQL_TABLE(app_label) + cur_app_label = app_label + if not self.dense: + print style.NOTICE("|-+ Differences for model:"), style.SQL_TABLE(model_name) + for diff in diffs: + diff_type, diff_args = diff + text = self.DIFF_TEXTS[diff_type] % dict((str(i), style.SQL_TABLE(e)) for i, e in enumerate(diff_args)) + text = "'".join(i%2==0 and style.ERROR_OUTPUT(e) or e for i, e in enumerate(text.split("'"))) + if not self.dense: + print style.NOTICE("|--+"), text + else: + print style.NOTICE("App"), style.SQL_TABLE(app_name), style.NOTICE('Model'), style.SQL_TABLE(model_name), text + + def print_diff_sql(self, style): + cur_app_label = None + qn = connection.ops.quote_name + print style.SQL_KEYWORD("BEGIN;") + for app_label, model_name, diffs in self.differences: + if not diffs: continue + if not self.dense and cur_app_label != app_label: + print style.NOTICE("-- Application: %s" % style.SQL_TABLE(app_label)) + cur_app_label = app_label + if not self.dense: + print style.NOTICE("-- Model: %s" % style.SQL_TABLE(model_name)) + for diff in diffs: + diff_type, diff_args = diff + text = self.DIFF_SQL[diff_type](style, qn, diff_args) + if self.dense: + text = text.replace("\n\t", " ") + print text + print style.SQL_KEYWORD("COMMIT;") + +class GenericSQLDiff(SQLDiff): + pass + +class MySQLDiff(SQLDiff): + # All the MySQL hacks together create something of a problem + # Fixing one bug in MySQL creates another issue. So just keep in mind + # that this is way unreliable for MySQL atm. + def get_field_db_type(self, description, field=None, table_name=None): + from MySQLdb.constants import FIELD_TYPE + # weird bug? in mysql db-api where it returns three times the correct value for field length + # if i remember correctly it had something todo with unicode strings + # TODO: Fix this is a more meaningful and better understood manner + description = list(description) + if description[1] not in [FIELD_TYPE.TINY, FIELD_TYPE.SHORT]: # exclude tinyints from conversion. + description[3] = description[3]/3 + description[4] = description[4]/3 + db_type = super(MySQLDiff, self).get_field_db_type(description) + if not db_type: + return + if field: + if field.primary_key and db_type=='integer': + db_type += ' AUTO_INCREMENT' + # MySQL isn't really sure about char's and varchar's like sqlite + field_type = self.get_field_model_type(field) + # Fix char/varchar inconsistencies + if self.strip_parameters(field_type)=='char' and self.strip_parameters(db_type)=='varchar': + db_type = db_type.lstrip("var") + # They like to call 'bool's 'tinyint(1)' and introspection makes that a integer + # just convert it back to it's proper type, a bool is a bool and nothing else. + if db_type=='integer' and description[1]==FIELD_TYPE.TINY and description[4]==1: + db_type = 'bool' + if db_type=='integer' and description[1]==FIELD_TYPE.SHORT: + db_type = 'smallint UNSIGNED' # FIXME: what about if it's not UNSIGNED ? + return db_type + +class SqliteSQLDiff(SQLDiff): + # Unique does not seem to be implied on Sqlite for Primary_key's + # if this is more generic among databases this might be usefull + # to add to the superclass's find_unique_missing_in_db method + def find_unique_missing_in_db(self, meta, table_indexes, table_name): + for field in meta.fields: + if field.unique: + attname = field.attname + if attname in table_indexes and table_indexes[attname]['unique']: + continue + if table_indexes[attname]['primary_key']: + continue + self.add_difference('unique-missing-in-db', table_name, attname) + + # Finding Indexes by using the get_indexes dictionary doesn't seem to work + # for sqlite. + def find_index_missing_in_db(self, meta, table_indexes, table_name): + pass + + def find_index_missing_in_model(self, meta, table_indexes, table_name): + pass + + def get_field_db_type(self, description, field=None, table_name=None): + db_type = super(SqliteSQLDiff, self).get_field_db_type(description) + if not db_type: + return + if field: + field_type = self.get_field_model_type(field) + # Fix char/varchar inconsistencies + if self.strip_parameters(field_type)=='char' and self.strip_parameters(db_type)=='varchar': + db_type = db_type.lstrip("var") + return db_type + +class PostgresqlSQLDiff(SQLDiff): + DATA_TYPES_REVERSE_OVERRIDE = { + 20: 'IntegerField', + 1042: 'CharField', + } + + # Hopefully in the future we can add constraint checking and other more + # advanced checks based on this database. + SQL_LOAD_CONSTRAINTS = """ + SELECT nspname, relname, conname, attname, pg_get_constraintdef(pg_constraint.oid) + FROM pg_constraint + INNER JOIN pg_attribute ON pg_constraint.conrelid = pg_attribute.attrelid AND pg_attribute.attnum = any(pg_constraint.conkey) + INNER JOIN pg_class ON conrelid=pg_class.oid + INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace + ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END,contype,nspname,relname,conname; + """ + + SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2])) + SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2])) + + def __init__(self, app_models, options): + SQLDiff.__init__(self, app_models, options) + self.check_constraints = {} + self.load_constraints() + + def load_constraints(self): + for dct in self.sql_to_dict(self.SQL_LOAD_CONSTRAINTS, []): + key = (dct['nspname'], dct['relname'], dct['attname']) + if 'CHECK' in dct['pg_get_constraintdef']: + self.check_constraints[key] = dct + + def get_field_db_type(self, description, field=None, table_name=None): + db_type = super(PostgresqlSQLDiff, self).get_field_db_type(description) + if not db_type: + return + if field: + if field.primary_key and db_type=='integer': + db_type = 'serial' + if table_name: + tablespace = field.db_tablespace + if tablespace=="": + tablespace = "public" + check_constraint = self.check_constraints.get((tablespace, table_name, field.attname),{}).get('pg_get_constraintdef', None) + if check_constraint: + check_constraint = check_constraint.replace("((", "(") + check_constraint = check_constraint.replace("))", ")") + check_constraint = '("'.join([')' in e and '" '.join(e.split(" ", 1)) or e for e in check_constraint.split("(")]) + # TODO: might be more then one constraint in definition ? + db_type += ' '+check_constraint + return db_type + + """ + def find_field_type_differ(self, meta, table_description, table_name): + def callback(field, description, model_type, db_type): + if field.primary_key and db_type=='integer': + db_type = 'serial' + return model_type, db_type + super(PostgresqlSQLDiff, self).find_field_type_differs(meta, table_description, table_name, callback) + """ + +DATABASE_SQLDIFF_CLASSES = { + 'postgresql_psycopg2' : PostgresqlSQLDiff, + 'postgresql': PostgresqlSQLDiff, + 'mysql': MySQLDiff, + 'sqlite3': SqliteSQLDiff, + 'oracle': GenericSQLDiff +} + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--all-applications', '-a', action='store_true', dest='all_applications', + help="Automaticly include all application from INSTALLED_APPS."), + make_option('--not-only-existing', '-e', action='store_false', dest='only_existing', + help="Check all tables that exist in the database, not only tables that should exist based on models."), + make_option('--dense-output', '-d', action='store_true', dest='dense_output', + help="Shows the output in dense format, normally output is spreaded over multiple lines."), + make_option('--output_text', '-t', action='store_false', dest='sql', default=True, + help="Outputs the differences as descriptive text instead of SQL"), + ) + + help = """Prints the (approximated) difference between models and fields in the database for the given app name(s). + +It indicates how columns in the database are different from the sql that would +be generated by Django. This command is not a database migration tool. (Though +it can certainly help) It's purpose is to show the current differences as a way +to check/debug ur models compared to the real database tables and columns.""" + + output_transaction = False + args = '' + + def handle(self, *app_labels, **options): + from django.db import models + from django.conf import settings + + if settings.DATABASE_ENGINE =='dummy': + # This must be the "dummy" database backend, which means the user + # hasn't set DATABASE_ENGINE. + raise CommandError("Django doesn't know which syntax to use for your SQL statements,\n" + + "because you haven't specified the DATABASE_ENGINE setting.\n" + + "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.") + + if options.get('all_applications', False): + app_models = models.get_models() + else: + if not app_labels: + raise CommandError('Enter at least one appname.') + try: + app_list = [models.get_app(app_label) for app_label in app_labels] + except (models.ImproperlyConfigured, ImportError), e: + raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e) + + app_models = [] + for app in app_list: + app_models.extend(models.get_models(app)) + + if not app_models: + raise CommandError('Unable to execute sqldiff no models founds.') + + cls = DATABASE_SQLDIFF_CLASSES.get(settings.DATABASE_ENGINE, GenericSQLDiff) + sqldiff_instance = cls(app_models, options) + sqldiff_instance.find_differences() + sqldiff_instance.print_diff(self.style) + return