Added first stab at 'django-admin.py inspectdb', which takes a database name and introspects the tables, outputting a Django model. Works in PostgreSQL and MySQL. It's missing some niceties at the moment, such as detection of primary-keys and relationships, but it works. Refs #90.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@384 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
1510ca1a80
commit
d9401b78f1
|
@ -7,6 +7,7 @@ ACTION_MAPPING = {
|
||||||
'adminindex': management.get_admin_index,
|
'adminindex': management.get_admin_index,
|
||||||
'createsuperuser': management.createsuperuser,
|
'createsuperuser': management.createsuperuser,
|
||||||
# 'dbcheck': management.database_check,
|
# 'dbcheck': management.database_check,
|
||||||
|
'inspectdb': management.inspectdb,
|
||||||
'runserver': management.runserver,
|
'runserver': management.runserver,
|
||||||
'sql': management.get_sql_create,
|
'sql': management.get_sql_create,
|
||||||
'sqlall': management.get_sql_all,
|
'sqlall': management.get_sql_all,
|
||||||
|
@ -66,6 +67,17 @@ def main():
|
||||||
print_error("Your action, %r, was invalid." % action, sys.argv[0])
|
print_error("Your action, %r, was invalid." % action, sys.argv[0])
|
||||||
if action in ('createsuperuser', 'init'):
|
if action in ('createsuperuser', 'init'):
|
||||||
ACTION_MAPPING[action]()
|
ACTION_MAPPING[action]()
|
||||||
|
elif action == 'inspectdb':
|
||||||
|
try:
|
||||||
|
param = args[1]
|
||||||
|
except IndexError:
|
||||||
|
parser.print_usage_and_exit()
|
||||||
|
try:
|
||||||
|
for line in ACTION_MAPPING[action](param):
|
||||||
|
print line
|
||||||
|
except NotImplementedError:
|
||||||
|
sys.stderr.write("Error: %r isn't supported for the currently selected database backend." % action)
|
||||||
|
sys.exit(1)
|
||||||
elif action in ('startapp', 'startproject'):
|
elif action in ('startapp', 'startproject'):
|
||||||
try:
|
try:
|
||||||
name = args[1]
|
name = args[1]
|
||||||
|
|
|
@ -37,5 +37,7 @@ dictfetchall = dbmod.dictfetchall
|
||||||
get_last_insert_id = dbmod.get_last_insert_id
|
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
|
||||||
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
|
||||||
|
|
|
@ -68,6 +68,11 @@ def get_date_trunc_sql(lookup_type, field_name):
|
||||||
subtractions.append(" - interval (DATE_FORMAT(%s, '%%%%m')-1) month" % field_name)
|
subtractions.append(" - interval (DATE_FORMAT(%s, '%%%%m')-1) month" % field_name)
|
||||||
return "(%s - %s)" % (field_name, ''.join(subtractions))
|
return "(%s - %s)" % (field_name, ''.join(subtractions))
|
||||||
|
|
||||||
|
def get_table_list(cursor):
|
||||||
|
"Returns a list of table names in the current database."
|
||||||
|
cursor.execute("SHOW TABLES")
|
||||||
|
return [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
OPERATOR_MAPPING = {
|
OPERATOR_MAPPING = {
|
||||||
'exact': '=',
|
'exact': '=',
|
||||||
'iexact': 'LIKE',
|
'iexact': 'LIKE',
|
||||||
|
@ -115,3 +120,23 @@ DATA_TYPES = {
|
||||||
'USStateField': 'varchar(2)',
|
'USStateField': 'varchar(2)',
|
||||||
'XMLField': 'text',
|
'XMLField': 'text',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DATA_TYPES_REVERSE = {
|
||||||
|
FIELD_TYPE.BLOB: 'TextField',
|
||||||
|
FIELD_TYPE.CHAR: 'CharField',
|
||||||
|
FIELD_TYPE.DECIMAL: 'FloatField',
|
||||||
|
FIELD_TYPE.DATE: 'DateField',
|
||||||
|
FIELD_TYPE.DATETIME: 'DateTimeField',
|
||||||
|
FIELD_TYPE.DOUBLE: 'FloatField',
|
||||||
|
FIELD_TYPE.FLOAT: 'FloatField',
|
||||||
|
FIELD_TYPE.INT24: 'IntegerField',
|
||||||
|
FIELD_TYPE.LONG: 'IntegerField',
|
||||||
|
FIELD_TYPE.LONGLONG: 'IntegerField',
|
||||||
|
FIELD_TYPE.SHORT: 'IntegerField',
|
||||||
|
FIELD_TYPE.STRING: 'TextField',
|
||||||
|
FIELD_TYPE.TIMESTAMP: 'DateTimeField',
|
||||||
|
FIELD_TYPE.TINY_BLOB: 'TextField',
|
||||||
|
FIELD_TYPE.MEDIUM_BLOB: 'TextField',
|
||||||
|
FIELD_TYPE.LONG_BLOB: 'TextField',
|
||||||
|
FIELD_TYPE.VAR_STRING: 'CharField',
|
||||||
|
}
|
||||||
|
|
|
@ -71,6 +71,17 @@ def get_date_trunc_sql(lookup_type, field_name):
|
||||||
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
# http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
||||||
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
|
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
|
||||||
|
|
||||||
|
def get_table_list(cursor):
|
||||||
|
"Returns a list of table names in the current database."
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT c.relname
|
||||||
|
FROM pg_catalog.pg_class c
|
||||||
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
WHERE c.relkind IN ('r', 'v', '')
|
||||||
|
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
||||||
|
AND pg_catalog.pg_table_is_visible(c.oid)""")
|
||||||
|
return [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -129,3 +140,19 @@ DATA_TYPES = {
|
||||||
'USStateField': 'varchar(2)',
|
'USStateField': 'varchar(2)',
|
||||||
'XMLField': 'text',
|
'XMLField': 'text',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Maps type codes to Django Field types.
|
||||||
|
DATA_TYPES_REVERSE = {
|
||||||
|
16: 'BooleanField',
|
||||||
|
21: 'SmallIntegerField',
|
||||||
|
23: 'IntegerField',
|
||||||
|
25: 'TextField',
|
||||||
|
869: 'IPAddressField',
|
||||||
|
1043: 'CharField',
|
||||||
|
1082: 'DateField',
|
||||||
|
1083: 'TimeField',
|
||||||
|
1114: 'DateTimeField',
|
||||||
|
1184: 'DateTimeField',
|
||||||
|
1266: 'TimeField',
|
||||||
|
1700: 'FloatField',
|
||||||
|
}
|
||||||
|
|
|
@ -109,6 +109,9 @@ def _sqlite_date_trunc(lookup_type, dt):
|
||||||
elif lookup_type == 'day':
|
elif lookup_type == 'day':
|
||||||
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
|
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
|
||||||
|
|
||||||
|
def get_table_list(cursor):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
# Operators and fields ########################################################
|
# Operators and fields ########################################################
|
||||||
|
|
||||||
OPERATOR_MAPPING = {
|
OPERATOR_MAPPING = {
|
||||||
|
@ -157,3 +160,5 @@ DATA_TYPES = {
|
||||||
'USStateField': 'varchar(2)',
|
'USStateField': 'varchar(2)',
|
||||||
'XMLField': 'text',
|
'XMLField': 'text',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DATA_TYPES_REVERSE = {}
|
||||||
|
|
|
@ -430,6 +430,32 @@ def createsuperuser():
|
||||||
print "User created successfully."
|
print "User created successfully."
|
||||||
createsuperuser.args = ''
|
createsuperuser.args = ''
|
||||||
|
|
||||||
|
def inspectdb(db_name):
|
||||||
|
"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.conf import settings
|
||||||
|
settings.DATABASE_NAME = db_name
|
||||||
|
cursor = db.db.cursor()
|
||||||
|
yield 'from django.core import meta'
|
||||||
|
yield ''
|
||||||
|
for table_name in db.get_table_list(cursor):
|
||||||
|
object_name = table_name.title().replace('_', '')
|
||||||
|
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 ' fields = ('
|
||||||
|
cursor.execute("SELECT * FROM %s LIMIT 1" % table_name)
|
||||||
|
for row in cursor.description:
|
||||||
|
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 ' )'
|
||||||
|
yield ''
|
||||||
|
inspectdb.help_doc = "Introspects the database tables in the given database and outputs a Django model module."
|
||||||
|
inspectdb.args = "[dbname]"
|
||||||
|
|
||||||
def runserver(port):
|
def runserver(port):
|
||||||
"Starts a lightweight Web server for development."
|
"Starts a lightweight Web server for development."
|
||||||
from django.core.servers.basehttp import run, WSGIServerException
|
from django.core.servers.basehttp import run, WSGIServerException
|
||||||
|
|
Loading…
Reference in New Issue