Fixed #1286 -- Improved 'inspectdb' so that it introspects primary_key=True and unique=True for MySQL. Thanks, gandalf@owca.info

git-svn-id: http://code.djangoproject.com/svn/django/trunk@2346 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-02-18 21:26:28 +00:00
parent b46128225d
commit ac97cf54af
8 changed files with 51 additions and 13 deletions

View File

@ -51,6 +51,7 @@ answer newbie questions, and generally made Django that much better:
deric@monowerks.com deric@monowerks.com
Jeremy Dunck <http://dunck.us/> Jeremy Dunck <http://dunck.us/>
Clint Ecker Clint Ecker
gandalf@owca.info
Baishampayan Ghose Baishampayan Ghose
Espen Grindhaug <http://grindhaug.org/> Espen Grindhaug <http://grindhaug.org/>
Gustavo Picon Gustavo Picon

View File

@ -37,6 +37,7 @@ get_random_function_sql = dbmod.get_random_function_sql
get_table_list = dbmod.get_table_list get_table_list = dbmod.get_table_list
get_table_description = dbmod.get_table_description get_table_description = dbmod.get_table_description
get_relations = dbmod.get_relations get_relations = dbmod.get_relations
get_indexes = dbmod.get_indexes
OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING
DATA_TYPES = dbmod.DATA_TYPES DATA_TYPES = dbmod.DATA_TYPES
DATA_TYPES_REVERSE = dbmod.DATA_TYPES_REVERSE DATA_TYPES_REVERSE = dbmod.DATA_TYPES_REVERSE

View File

@ -118,6 +118,9 @@ def get_table_description(cursor, table_name):
def get_relations(cursor, table_name): def get_relations(cursor, table_name):
raise NotImplementedError raise NotImplementedError
def get_indexes(cursor, table_name):
raise NotImplementedError
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',
'iexact': 'LIKE %s', 'iexact': 'LIKE %s',

View File

@ -135,6 +135,14 @@ def get_table_description(cursor, table_name):
def get_relations(cursor, table_name): def get_relations(cursor, table_name):
raise NotImplementedError raise NotImplementedError
def get_indexes(cursor, table_name):
"Returns a dict of indexes for given table"
cursor.execute("SHOW INDEX FROM %s" % DatabaseWrapper().quote_name(table_name))
indexes = {}
for row in cursor.fetchall():
indexes[row[4]] = {'keyname': row[2], 'unique': not bool(row[1])}
return indexes
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',
'iexact': 'LIKE %s', 'iexact': 'LIKE %s',

View File

@ -126,6 +126,9 @@ def get_relations(cursor, table_name):
continue continue
return relations return relations
def get_indexes(cursor, table_name):
raise NotImplementedError
# Register these custom typecasts, because Django expects dates/times to be # Register these custom typecasts, because Django expects dates/times to be
# in Python's native (standard-library) datetime/time format, whereas psycopg # in Python's native (standard-library) datetime/time format, whereas psycopg
# use mx.DateTime by default. # use mx.DateTime by default.

View File

@ -134,6 +134,9 @@ def get_table_description(cursor, table_name):
def get_relations(cursor, table_name): def get_relations(cursor, table_name):
raise NotImplementedError raise NotImplementedError
def get_indexes(cursor, table_name):
raise NotImplementedError
# Operators and fields ######################################################## # Operators and fields ########################################################
# SQLite requires LIKE statements to include an ESCAPE clause if the value # SQLite requires LIKE statements to include an ESCAPE clause if the value

View File

@ -595,23 +595,27 @@ def inspectdb(db_name):
relations = db.get_relations(cursor, table_name) relations = db.get_relations(cursor, table_name)
except NotImplementedError: except NotImplementedError:
relations = {} relations = {}
try:
indexes = db.get_indexes(cursor, table_name)
except NotImplementedError:
indexes = {}
for i, row in enumerate(db.get_table_description(cursor, table_name)): for i, row in enumerate(db.get_table_description(cursor, table_name)):
column_name = row[0] att_name = row[0]
comment_notes = [] # Holds Field notes, to be displayed in a Python comment. comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
extra_params = {} # Holds Field parameters such as 'db_column'. extra_params = {} # Holds Field parameters such as 'db_column'.
if keyword.iskeyword(column_name): if keyword.iskeyword(att_name):
extra_params['db_column'] = column_name extra_params['db_column'] = att_name
column_name += '_field' att_name += '_field'
comment_notes.append('Field renamed because it was a Python reserved word.') comment_notes.append('Field renamed because it was a Python reserved word.')
if relations.has_key(i): if relations.has_key(i):
rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1]) rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
field_type = 'ForeignKey(%s' % rel_to field_type = 'ForeignKey(%s' % rel_to
if column_name.endswith('_id'): if att_name.endswith('_id'):
column_name = column_name[:-3] att_name = att_name[:-3]
else: else:
extra_params['db_column'] = column_name extra_params['db_column'] = att_name
else: else:
try: try:
field_type = db.DATA_TYPES_REVERSE[row[1]] field_type = db.DATA_TYPES_REVERSE[row[1]]
@ -625,12 +629,26 @@ def inspectdb(db_name):
field_type, new_params = field_type field_type, new_params = field_type
extra_params.update(new_params) extra_params.update(new_params)
# Add maxlength for all CharFields.
if field_type == 'CharField' and row[3]: if field_type == 'CharField' and row[3]:
extra_params['maxlength'] = row[3] extra_params['maxlength'] = row[3]
# Add primary_key and unique, if necessary.
column_name = extra_params.get('db_column', att_name)
if column_name in indexes:
if indexes[column_name]['keyname'] == 'PRIMARY':
extra_params['primary_key'] = True
elif indexes[column_name]['unique']:
extra_params['unique'] = True
field_type += '(' field_type += '('
field_desc = '%s = meta.%s' % (column_name, field_type) # Don't output 'id = meta.AutoField(primary_key=True)', because
# that's assumed if it doesn't exist.
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
continue
field_desc = '%s = meta.%s' % (att_name, field_type)
if extra_params: if extra_params:
if not field_desc.endswith('('): if not field_desc.endswith('('):
field_desc += ', ' field_desc += ', '

View File

@ -112,12 +112,13 @@ output:
This feature is meant as a shortcut, not as definitive model generation. After This feature is meant as a shortcut, not as definitive model generation. After
you run it, you'll want to look over the generated models yourself to make you run it, you'll want to look over the generated models yourself to make
customizations. In particular, you'll need to do this: customizations. In particular, you'll need to rearrange models' order, so that
models that refer to other models are ordered properly.
* Rearrange models' order, so that models that refer to other models are If you're using Django 0.90 or 0.91, you'll need to add ``primary_key=True`` to
ordered properly. one field in each model. In the Django development version, primary keys are
* Add ``primary_key=True`` to one field in each model. The ``inspectdb`` automatically introspected for MySQL, and Django puts in the
doesn't yet introspect primary keys. ``primary_key=True`` where needed.
``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection ``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection
only works in PostgreSQL. only works in PostgreSQL.