diff --git a/AUTHORS b/AUTHORS
index b84a51fbed..e5d43deaae 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -51,6 +51,7 @@ answer newbie questions, and generally made Django that much better:
deric@monowerks.com
Jeremy Dunck
Clint Ecker
+ gandalf@owca.info
Baishampayan Ghose
Espen Grindhaug
Gustavo Picon
diff --git a/django/core/management.py b/django/core/management.py
index 1bbe8d7444..48ea67b8a2 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -752,7 +752,7 @@ def inspectdb(db_name):
yield "# This is an auto-generated Django model module."
yield "# You'll have to do the following manually to clean this up:"
yield "# * Rearrange models' order"
- yield "# * Add primary_key=True to one field in each model."
+ yield "# * Make sure each model has one field with primary_key=True"
yield "# Feel free to rename the models, but don't rename db_table values or field names."
yield "#"
yield "# Also note: You'll have to insert the output of 'django-admin.py sqlinitialdata [appname]'"
@@ -766,23 +766,27 @@ def inspectdb(db_name):
relations = introspection_module.get_relations(cursor, table_name)
except NotImplementedError:
relations = {}
+ try:
+ indexes = introspection_module.get_indexes(cursor, table_name)
+ except NotImplementedError:
+ indexes = {}
for i, row in enumerate(introspection_module.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.
extra_params = {} # Holds Field parameters such as 'db_column'.
- if keyword.iskeyword(column_name):
- extra_params['db_column'] = column_name
- column_name += '_field'
+ if keyword.iskeyword(att_name):
+ extra_params['db_column'] = att_name
+ att_name += '_field'
comment_notes.append('Field renamed because it was a Python reserved word.')
if relations.has_key(i):
rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
field_type = 'ForeignKey(%s' % rel_to
- if column_name.endswith('_id'):
- column_name = column_name[:-3]
+ if att_name.endswith('_id'):
+ att_name = att_name[:-3]
else:
- extra_params['db_column'] = column_name
+ extra_params['db_column'] = att_name
else:
try:
field_type = introspection_module.DATA_TYPES_REVERSE[row[1]]
@@ -797,13 +801,26 @@ def inspectdb(db_name):
field_type, new_params = field_type
extra_params.update(new_params)
+ # Add maxlength for all CharFields.
if field_type == 'CharField' and 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]['primary_key']:
+ extra_params['primary_key'] = True
+ elif indexes[column_name]['unique']:
+ extra_params['unique'] = True
+
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 = models.%s' % (column_name, field_type)
+ field_desc = '%s = models.%s' % (att_name, field_type)
if extra_params:
if not field_desc.endswith('('):
field_desc += ', '
diff --git a/django/db/backends/ado_mssql/introspection.py b/django/db/backends/ado_mssql/introspection.py
index a193b59cc1..b125cc995f 100644
--- a/django/db/backends/ado_mssql/introspection.py
+++ b/django/db/backends/ado_mssql/introspection.py
@@ -7,4 +7,7 @@ def get_table_description(cursor, table_name):
def get_relations(cursor, table_name):
raise NotImplementedError
+def get_indexes(cursor, table_name):
+ raise NotImplementedError
+
DATA_TYPES_REVERSE = {}
diff --git a/django/db/backends/dummy/introspection.py b/django/db/backends/dummy/introspection.py
index 81dc8efdf0..c52a812046 100644
--- a/django/db/backends/dummy/introspection.py
+++ b/django/db/backends/dummy/introspection.py
@@ -3,5 +3,6 @@ from django.db.backends.dummy.base import complain
get_table_list = complain
get_table_description = complain
get_relations = complain
+get_indexes = complain
DATA_TYPES_REVERSE = {}
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index d33b5bbfe1..b4389e6030 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -14,6 +14,19 @@ def get_table_description(cursor, table_name):
def get_relations(cursor, table_name):
raise NotImplementedError
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
+ indexes = {}
+ for row in cursor.fetchall():
+ indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
+ return indexes
+
DATA_TYPES_REVERSE = {
FIELD_TYPE.BLOB: 'TextField',
FIELD_TYPE.CHAR: 'CharField',
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
index c06b523211..63893bd15b 100644
--- a/django/db/backends/postgresql/introspection.py
+++ b/django/db/backends/postgresql/introspection.py
@@ -37,6 +37,36 @@ def get_relations(cursor, table_name):
continue
return relations
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ # Get the table description because we only have the column indexes, and we
+ # need the column names.
+ desc = get_table_description(cursor, table_name)
+ # This query retrieves each index on the given table.
+ cursor.execute("""
+ SELECT idx.indkey, idx.indisunique, idx.indisprimary
+ FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
+ pg_catalog.pg_index idx
+ WHERE c.oid = idx.indrelid
+ AND idx.indexrelid = c2.oid
+ AND c.relname = %s""", [table_name])
+ indexes = {}
+ for row in cursor.fetchall():
+ # row[0] (idx.indkey) is stored in the DB as an array. It comes out as
+ # a string of space-separated integers. This designates the field
+ # indexes (1-based) of the fields that have indexes on the table.
+ # Here, we skip any indexes across multiple fields.
+ if ' ' in row[0]:
+ continue
+ col_name = desc[int(row[0])-1][0]
+ indexes[col_name] = {'primary_key': row[2], 'unique': row[1]}
+ return indexes
+
# Maps type codes to Django Field types.
DATA_TYPES_REVERSE = {
16: 'BooleanField',
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index 26b7cc0695..0ca971c0d4 100644
--- a/django/db/backends/sqlite3/introspection.py
+++ b/django/db/backends/sqlite3/introspection.py
@@ -11,6 +11,9 @@ def get_table_description(cursor, table_name):
def get_relations(cursor, table_name):
raise NotImplementedError
+def get_indexes(cursor, table_name):
+ raise NotImplementedError
+
# Maps SQL types to Django Field types. Some of the SQL types have multiple
# entries here because SQLite allows for anything and doesn't normalize the
# field type; it uses whatever was given.
diff --git a/docs/django-admin.txt b/docs/django-admin.txt
index 36ebe8ba67..45cc2363dc 100644
--- a/docs/django-admin.txt
+++ b/docs/django-admin.txt
@@ -112,12 +112,13 @@ output:
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
-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
- ordered properly.
- * Add ``primary_key=True`` to one field in each model. The ``inspectdb``
- doesn't yet introspect primary keys.
+If you're using Django 0.90 or 0.91, you'll need to add ``primary_key=True`` to
+one field in each model. In the Django development version, primary keys are
+automatically introspected for PostgreSQL and MySQL, and Django puts in the
+``primary_key=True`` where needed.
``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection
only works in PostgreSQL.