Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added first stab at 'django-admin.py inspectdb', which takes a databa…

…se 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
  • Loading branch information...
commit d9401b78f11b47c5daa95a4fa60925b3fed50203 1 parent 1510ca1
Adrian Holovaty authored
12  django/bin/django-admin.py
@@ -7,6 +7,7 @@
7 7
     'adminindex': management.get_admin_index,
8 8
     'createsuperuser': management.createsuperuser,
9 9
 #     'dbcheck': management.database_check,
  10
+    'inspectdb': management.inspectdb,
10 11
     'runserver': management.runserver,
11 12
     'sql': management.get_sql_create,
12 13
     'sqlall': management.get_sql_all,
@@ -66,6 +67,17 @@ def main():
66 67
         print_error("Your action, %r, was invalid." % action, sys.argv[0])
67 68
     if action in ('createsuperuser', 'init'):
68 69
         ACTION_MAPPING[action]()
  70
+    elif action == 'inspectdb':
  71
+        try:
  72
+            param = args[1]
  73
+        except IndexError:
  74
+            parser.print_usage_and_exit()
  75
+        try:
  76
+            for line in ACTION_MAPPING[action](param):
  77
+                print line
  78
+        except NotImplementedError:
  79
+            sys.stderr.write("Error: %r isn't supported for the currently selected database backend." % action)
  80
+            sys.exit(1)
69 81
     elif action in ('startapp', 'startproject'):
70 82
         try:
71 83
             name = args[1]
2  django/core/db/__init__.py
@@ -37,5 +37,7 @@
37 37
 get_last_insert_id = dbmod.get_last_insert_id
38 38
 get_date_extract_sql = dbmod.get_date_extract_sql
39 39
 get_date_trunc_sql = dbmod.get_date_trunc_sql
  40
+get_table_list = dbmod.get_table_list
40 41
 OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING
41 42
 DATA_TYPES = dbmod.DATA_TYPES
  43
+DATA_TYPES_REVERSE = dbmod.DATA_TYPES_REVERSE
25  django/core/db/backends/mysql.py
@@ -68,6 +68,11 @@ def get_date_trunc_sql(lookup_type, field_name):
68 68
         subtractions.append(" - interval (DATE_FORMAT(%s, '%%%%m')-1) month" % field_name)
69 69
     return "(%s - %s)" % (field_name, ''.join(subtractions))
70 70
 
  71
+def get_table_list(cursor):
  72
+    "Returns a list of table names in the current database."
  73
+    cursor.execute("SHOW TABLES")
  74
+    return [row[0] for row in cursor.fetchall()]
  75
+
71 76
 OPERATOR_MAPPING = {
72 77
     'exact': '=',
73 78
     'iexact': 'LIKE',
@@ -115,3 +120,23 @@ def get_date_trunc_sql(lookup_type, field_name):
115 120
     'USStateField':      'varchar(2)',
116 121
     'XMLField':          'text',
117 122
 }
  123
+
  124
+DATA_TYPES_REVERSE = {
  125
+    FIELD_TYPE.BLOB: 'TextField',
  126
+    FIELD_TYPE.CHAR: 'CharField',
  127
+    FIELD_TYPE.DECIMAL: 'FloatField',
  128
+    FIELD_TYPE.DATE: 'DateField',
  129
+    FIELD_TYPE.DATETIME: 'DateTimeField',
  130
+    FIELD_TYPE.DOUBLE: 'FloatField',
  131
+    FIELD_TYPE.FLOAT: 'FloatField',
  132
+    FIELD_TYPE.INT24: 'IntegerField',
  133
+    FIELD_TYPE.LONG: 'IntegerField',
  134
+    FIELD_TYPE.LONGLONG: 'IntegerField',
  135
+    FIELD_TYPE.SHORT: 'IntegerField',
  136
+    FIELD_TYPE.STRING: 'TextField',
  137
+    FIELD_TYPE.TIMESTAMP: 'DateTimeField',
  138
+    FIELD_TYPE.TINY_BLOB: 'TextField',
  139
+    FIELD_TYPE.MEDIUM_BLOB: 'TextField',
  140
+    FIELD_TYPE.LONG_BLOB: 'TextField',
  141
+    FIELD_TYPE.VAR_STRING: 'CharField',
  142
+}
27  django/core/db/backends/postgresql.py
@@ -71,6 +71,17 @@ def get_date_trunc_sql(lookup_type, field_name):
71 71
     # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
72 72
     return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)
73 73
 
  74
+def get_table_list(cursor):
  75
+    "Returns a list of table names in the current database."
  76
+    cursor.execute("""
  77
+        SELECT c.relname
  78
+        FROM pg_catalog.pg_class c
  79
+        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
  80
+        WHERE c.relkind IN ('r', 'v', '')
  81
+            AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
  82
+            AND pg_catalog.pg_table_is_visible(c.oid)""")
  83
+    return [row[0] for row in cursor.fetchall()]
  84
+
74 85
 # Register these custom typecasts, because Django expects dates/times to be
75 86
 # in Python's native (standard-library) datetime/time format, whereas psycopg
76 87
 # use mx.DateTime by default.
@@ -129,3 +140,19 @@ def get_date_trunc_sql(lookup_type, field_name):
129 140
     'USStateField':      'varchar(2)',
130 141
     'XMLField':          'text',
131 142
 }
  143
+
  144
+# Maps type codes to Django Field types.
  145
+DATA_TYPES_REVERSE = {
  146
+    16: 'BooleanField',
  147
+    21: 'SmallIntegerField',
  148
+    23: 'IntegerField',
  149
+    25: 'TextField',
  150
+    869: 'IPAddressField',
  151
+    1043: 'CharField',
  152
+    1082: 'DateField',
  153
+    1083: 'TimeField',
  154
+    1114: 'DateTimeField',
  155
+    1184: 'DateTimeField',
  156
+    1266: 'TimeField',
  157
+    1700: 'FloatField',
  158
+}
19  django/core/db/backends/sqlite3.py
@@ -61,15 +61,15 @@ class SQLiteCursorWrapper(Database.Cursor):
61 61
     This fixes it -- but note that if you want to use a literal "%s" in a query,
62 62
     you'll need to use "%%s" (which I belive is true of other wrappers as well).
63 63
     """
64  
-    
  64
+
65 65
     def execute(self, query, params=[]):
66 66
         query = self.convert_query(query, len(params))
67 67
         return Database.Cursor.execute(self, query, params)
68  
-        
  68
+
69 69
     def executemany(self, query, params=[]):
70 70
         query = self.convert_query(query, len(params[0]))
71 71
         return Database.Cursor.executemany(self, query, params)
72  
-        
  72
+
73 73
     def convert_query(self, query, num_params):
74 74
         # XXX this seems too simple to be correct... is this right?
75 75
         return query % tuple("?" * num_params)
@@ -78,10 +78,10 @@ def convert_query(self, query, num_params):
78 78
 
79 79
 def get_last_insert_id(cursor, table_name, pk_name):
80 80
     return cursor.lastrowid
81  
-    
  81
+
82 82
 def get_date_extract_sql(lookup_type, table_name):
83 83
     # lookup_type is 'year', 'month', 'day'
84  
-    # sqlite doesn't support extract, so we fake it with the user-defined 
  84
+    # sqlite doesn't support extract, so we fake it with the user-defined
85 85
     # function _sqlite_extract that's registered in connect(), above.
86 86
     return 'django_extract("%s", %s)' % (lookup_type.lower(), table_name)
87 87
 
@@ -109,8 +109,11 @@ def _sqlite_date_trunc(lookup_type, dt):
109 109
     elif lookup_type == 'day':
110 110
         return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
111 111
 
  112
+def get_table_list(cursor):
  113
+    raise NotImplementedError
  114
+
112 115
 # Operators and fields ########################################################
113  
-        
  116
+
114 117
 OPERATOR_MAPPING = {
115 118
     'exact':        '=',
116 119
     'iexact':       'LIKE',
@@ -127,7 +130,7 @@ def _sqlite_date_trunc(lookup_type, dt):
127 130
     'iendswith':    'LIKE',
128 131
 }
129 132
 
130  
-# SQLite doesn't actually support most of these types, but it "does the right 
  133
+# SQLite doesn't actually support most of these types, but it "does the right
131 134
 # thing" given more verbose field definitions, so leave them as is so that
132 135
 # schema inspection is more useful.
133 136
 DATA_TYPES = {
@@ -157,3 +160,5 @@ def _sqlite_date_trunc(lookup_type, dt):
157 160
     'USStateField':                 'varchar(2)',
158 161
     'XMLField':                     'text',
159 162
 }
  163
+
  164
+DATA_TYPES_REVERSE = {}
26  django/core/management.py
@@ -430,6 +430,32 @@ def createsuperuser():
430 430
     print "User created successfully."
431 431
 createsuperuser.args = ''
432 432
 
  433
+def inspectdb(db_name):
  434
+    "Generator that introspects the tables in the given database name and returns a Django model, one line at a time."
  435
+    from django.core import db
  436
+    from django.conf import settings
  437
+    settings.DATABASE_NAME = db_name
  438
+    cursor = db.db.cursor()
  439
+    yield 'from django.core import meta'
  440
+    yield ''
  441
+    for table_name in db.get_table_list(cursor):
  442
+        object_name = table_name.title().replace('_', '')
  443
+        object_name = object_name.endswith('s') and object_name[:-1] or object_name
  444
+        yield 'class %s(meta.Model):' % object_name
  445
+        yield '    db_table = %r' % table_name
  446
+        yield '    fields = ('
  447
+        cursor.execute("SELECT * FROM %s LIMIT 1" % table_name)
  448
+        for row in cursor.description:
  449
+            field_type = db.DATA_TYPES_REVERSE[row[1]]
  450
+            field_desc = 'meta.%s(%r' % (field_type, row[0])
  451
+            if field_type == 'CharField':
  452
+                field_desc += ', maxlength=%s' % (row[3])
  453
+            yield '        %s),' % field_desc
  454
+        yield '    )'
  455
+        yield ''
  456
+inspectdb.help_doc = "Introspects the database tables in the given database and outputs a Django model module."
  457
+inspectdb.args = "[dbname]"
  458
+
433 459
 def runserver(port):
434 460
     "Starts a lightweight Web server for development."
435 461
     from django.core.servers.basehttp import run, WSGIServerException

0 notes on commit d9401b7

Please sign in to comment.
Something went wrong with that request. Please try again.