--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/core/management/commands/inspectdb.py Wed Jun 02 18:57:35 2010 +0200
@@ -0,0 +1,164 @@
+import keyword
+from optparse import make_option
+
+from django.core.management.base import NoArgsCommand, CommandError
+from django.db import connections, DEFAULT_DB_ALIAS
+
+class Command(NoArgsCommand):
+ help = "Introspects the database tables in the given database and outputs a Django model module."
+
+ option_list = NoArgsCommand.option_list + (
+ make_option('--database', action='store', dest='database',
+ default=DEFAULT_DB_ALIAS, help='Nominates a database to '
+ 'introspect. Defaults to using the "default" database.'),
+ )
+
+ requires_model_validation = False
+
+ db_module = 'django.db'
+
+ def handle_noargs(self, **options):
+ try:
+ for line in self.handle_inspection(options):
+ print line
+ except NotImplementedError:
+ raise CommandError("Database inspection isn't supported for the currently selected database backend.")
+
+ def handle_inspection(self, options):
+ connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
+
+ table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '')
+
+ cursor = connection.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 "# * 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 sqlcustom [appname]'"
+ yield "# into your database."
+ yield ''
+ yield 'from %s import models' % self.db_module
+ yield ''
+ for table_name in connection.introspection.get_table_list(cursor):
+ yield 'class %s(models.Model):' % table2model(table_name)
+ try:
+ relations = connection.introspection.get_relations(cursor, table_name)
+ except NotImplementedError:
+ relations = {}
+ try:
+ indexes = connection.introspection.get_indexes(cursor, table_name)
+ except NotImplementedError:
+ indexes = {}
+ for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
+ column_name = row[0]
+ att_name = column_name.lower()
+ comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
+ extra_params = {} # Holds Field parameters such as 'db_column'.
+
+ # If the column name can't be used verbatim as a Python
+ # attribute, set the "db_column" for this Field.
+ if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name:
+ extra_params['db_column'] = column_name
+
+ # Modify the field name to make it Python-compatible.
+ if ' ' in att_name:
+ att_name = att_name.replace(' ', '_')
+ comment_notes.append('Field renamed to remove spaces.')
+ if '-' in att_name:
+ att_name = att_name.replace('-', '_')
+ comment_notes.append('Field renamed to remove dashes.')
+ if keyword.iskeyword(att_name):
+ att_name += '_field'
+ comment_notes.append('Field renamed because it was a Python reserved word.')
+ if column_name != att_name:
+ comment_notes.append('Field name made lowercase.')
+
+ if i in relations:
+ rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
+ field_type = 'ForeignKey(%s' % rel_to
+ if att_name.endswith('_id'):
+ att_name = att_name[:-3]
+ else:
+ extra_params['db_column'] = column_name
+ else:
+ # Calling `get_field_type` to get the field type string and any
+ # additional paramters and notes.
+ field_type, field_params, field_notes = self.get_field_type(connection, table_name, row)
+ extra_params.update(field_params)
+ comment_notes.extend(field_notes)
+
+ # Add primary_key and unique, if necessary.
+ 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
+
+ # Add 'null' and 'blank', if the 'null_ok' flag was present in the
+ # table description.
+ if row[6]: # If it's NULL...
+ extra_params['blank'] = True
+ if not field_type in ('TextField(', 'CharField('):
+ extra_params['null'] = True
+
+ field_desc = '%s = models.%s' % (att_name, field_type)
+ if extra_params:
+ if not field_desc.endswith('('):
+ field_desc += ', '
+ field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()])
+ field_desc += ')'
+ if comment_notes:
+ field_desc += ' # ' + ' '.join(comment_notes)
+ yield ' %s' % field_desc
+ for meta_line in self.get_meta(table_name):
+ yield meta_line
+
+ def get_field_type(self, connection, table_name, row):
+ """
+ Given the database connection, the table name, and the cursor row
+ description, this routine will return the given field type name, as
+ well as any additional keyword parameters and notes for the field.
+ """
+ field_params = {}
+ field_notes = []
+
+ try:
+ field_type = connection.introspection.get_field_type(row[1], row)
+ except KeyError:
+ field_type = 'TextField'
+ field_notes.append('This field type is a guess.')
+
+ # This is a hook for DATA_TYPES_REVERSE to return a tuple of
+ # (field_type, field_params_dict).
+ if type(field_type) is tuple:
+ field_type, new_params = field_type
+ field_params.update(new_params)
+
+ # Add max_length for all CharFields.
+ if field_type == 'CharField' and row[3]:
+ field_params['max_length'] = row[3]
+
+ if field_type == 'DecimalField':
+ field_params['max_digits'] = row[4]
+ field_params['decimal_places'] = row[5]
+
+ return field_type, field_params, field_notes
+
+ def get_meta(self, table_name):
+ """
+ Return a sequence comprising the lines of code necessary
+ to construct the inner Meta class for the model corresponding
+ to the given database table name.
+ """
+ return [' class Meta:',
+ ' db_table = %r' % table_name,
+ '']