Improved 'django-admin inspectdb' so that it detects ForeignKey relationships -- PostgreSQL only
git-svn-id: http://code.djangoproject.com/svn/django/trunk@395 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
c0451036ac
commit
f4e4ae96cb
|
@ -76,7 +76,7 @@ def main():
|
||||||
for line in ACTION_MAPPING[action](param):
|
for line in ACTION_MAPPING[action](param):
|
||||||
print line
|
print line
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
sys.stderr.write("Error: %r isn't supported for the currently selected database backend." % action)
|
sys.stderr.write("Error: %r isn't supported for the currently selected database backend.\n" % action)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif action in ('startapp', 'startproject'):
|
elif action in ('startapp', 'startproject'):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -38,6 +38,7 @@ get_last_insert_id = dbmod.get_last_insert_id
|
||||||
get_date_extract_sql = dbmod.get_date_extract_sql
|
get_date_extract_sql = dbmod.get_date_extract_sql
|
||||||
get_date_trunc_sql = dbmod.get_date_trunc_sql
|
get_date_trunc_sql = dbmod.get_date_trunc_sql
|
||||||
get_table_list = dbmod.get_table_list
|
get_table_list = dbmod.get_table_list
|
||||||
|
get_relations = dbmod.get_relations
|
||||||
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
|
||||||
|
|
|
@ -73,6 +73,9 @@ def get_table_list(cursor):
|
||||||
cursor.execute("SHOW TABLES")
|
cursor.execute("SHOW TABLES")
|
||||||
return [row[0] for row in cursor.fetchall()]
|
return [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
def get_relations(cursor, table_name):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
OPERATOR_MAPPING = {
|
OPERATOR_MAPPING = {
|
||||||
'exact': '=',
|
'exact': '=',
|
||||||
'iexact': 'LIKE',
|
'iexact': 'LIKE',
|
||||||
|
|
|
@ -82,6 +82,27 @@ def get_table_list(cursor):
|
||||||
AND pg_catalog.pg_table_is_visible(c.oid)""")
|
AND pg_catalog.pg_table_is_visible(c.oid)""")
|
||||||
return [row[0] for row in cursor.fetchall()]
|
return [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
def get_relations(cursor, table_name):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of {field_index: (field_index_other_table, other_table)}
|
||||||
|
representing all relationships to the given table. Indexes are 0-based.
|
||||||
|
"""
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT con.conkey, con.confkey, c2.relname
|
||||||
|
FROM pg_constraint con, pg_class c1, pg_class c2
|
||||||
|
WHERE c1.oid = con.conrelid
|
||||||
|
AND c2.oid = con.confrelid
|
||||||
|
AND c1.relname = %s
|
||||||
|
AND con.contype = 'f'""", [table_name])
|
||||||
|
relations = {}
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
try:
|
||||||
|
# row[0] and row[1] are like "{2}", so strip the curly braces.
|
||||||
|
relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2])
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return relations
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
|
@ -112,6 +112,9 @@ def _sqlite_date_trunc(lookup_type, dt):
|
||||||
def get_table_list(cursor):
|
def get_table_list(cursor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_relations(cursor, table_name):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
# Operators and fields ########################################################
|
# Operators and fields ########################################################
|
||||||
|
|
||||||
OPERATOR_MAPPING = {
|
OPERATOR_MAPPING = {
|
||||||
|
|
|
@ -434,22 +434,40 @@ def inspectdb(db_name):
|
||||||
"Generator that introspects the tables in the given database name and returns a Django model, one line at a time."
|
"Generator that introspects the tables in the given database name and returns a Django model, one line at a time."
|
||||||
from django.core import db
|
from django.core import db
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
def table2model(table_name):
|
||||||
|
object_name = table_name.title().replace('_', '')
|
||||||
|
return object_name.endswith('s') and object_name[:-1] or object_name
|
||||||
|
|
||||||
settings.DATABASE_NAME = db_name
|
settings.DATABASE_NAME = db_name
|
||||||
cursor = db.db.cursor()
|
cursor = db.db.cursor()
|
||||||
|
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 "# Feel free to rename the models, but don't rename db_table values or field names."
|
||||||
|
yield ''
|
||||||
yield 'from django.core import meta'
|
yield 'from django.core import meta'
|
||||||
yield ''
|
yield ''
|
||||||
for table_name in db.get_table_list(cursor):
|
for table_name in db.get_table_list(cursor):
|
||||||
object_name = table_name.title().replace('_', '')
|
yield 'class %s(meta.Model):' % table2model(table_name)
|
||||||
object_name = object_name.endswith('s') and object_name[:-1] or object_name
|
|
||||||
yield 'class %s(meta.Model):' % object_name
|
|
||||||
yield ' db_table = %r' % table_name
|
yield ' db_table = %r' % table_name
|
||||||
yield ' fields = ('
|
yield ' fields = ('
|
||||||
|
try:
|
||||||
|
relations = db.get_relations(cursor, table_name)
|
||||||
|
except NotImplementedError:
|
||||||
|
relations = {}
|
||||||
cursor.execute("SELECT * FROM %s LIMIT 1" % table_name)
|
cursor.execute("SELECT * FROM %s LIMIT 1" % table_name)
|
||||||
for row in cursor.description:
|
for i, row in enumerate(cursor.description):
|
||||||
field_type = db.DATA_TYPES_REVERSE[row[1]]
|
if relations.has_key(i):
|
||||||
field_desc = 'meta.%s(%r' % (field_type, row[0])
|
rel = relations[i]
|
||||||
if field_type == 'CharField':
|
rel_to = rel[1] == table_name and "'self'" or table2model(rel[1])
|
||||||
field_desc += ', maxlength=%s' % (row[3])
|
field_desc = 'meta.ForeignKey(%s, name=%r' % (rel_to, row[0])
|
||||||
|
else:
|
||||||
|
field_type = db.DATA_TYPES_REVERSE[row[1]]
|
||||||
|
field_desc = 'meta.%s(%r' % (field_type, row[0])
|
||||||
|
if field_type == 'CharField':
|
||||||
|
field_desc += ', maxlength=%s' % (row[3])
|
||||||
yield ' %s),' % field_desc
|
yield ' %s),' % field_desc
|
||||||
yield ' )'
|
yield ' )'
|
||||||
yield ''
|
yield ''
|
||||||
|
|
Loading…
Reference in New Issue