web/lib/django/core/management/commands/inspectdb.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 import keyword
       
     2 from optparse import make_option
       
     3 
       
     4 from django.core.management.base import NoArgsCommand, CommandError
       
     5 from django.db import connections, DEFAULT_DB_ALIAS
       
     6 
       
     7 class Command(NoArgsCommand):
       
     8     help = "Introspects the database tables in the given database and outputs a Django model module."
       
     9 
       
    10     option_list = NoArgsCommand.option_list + (
       
    11         make_option('--database', action='store', dest='database',
       
    12             default=DEFAULT_DB_ALIAS, help='Nominates a database to '
       
    13                 'introspect.  Defaults to using the "default" database.'),
       
    14     )
       
    15 
       
    16     requires_model_validation = False
       
    17 
       
    18     db_module = 'django.db'
       
    19 
       
    20     def handle_noargs(self, **options):
       
    21         try:
       
    22             for line in self.handle_inspection(options):
       
    23                 print line
       
    24         except NotImplementedError:
       
    25             raise CommandError("Database inspection isn't supported for the currently selected database backend.")
       
    26 
       
    27     def handle_inspection(self, options):
       
    28         connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
       
    29 
       
    30         table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '')
       
    31 
       
    32         cursor = connection.cursor()
       
    33         yield "# This is an auto-generated Django model module."
       
    34         yield "# You'll have to do the following manually to clean this up:"
       
    35         yield "#     * Rearrange models' order"
       
    36         yield "#     * Make sure each model has one field with primary_key=True"
       
    37         yield "# Feel free to rename the models, but don't rename db_table values or field names."
       
    38         yield "#"
       
    39         yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
       
    40         yield "# into your database."
       
    41         yield ''
       
    42         yield 'from %s import models' % self.db_module
       
    43         yield ''
       
    44         for table_name in connection.introspection.get_table_list(cursor):
       
    45             yield 'class %s(models.Model):' % table2model(table_name)
       
    46             try:
       
    47                 relations = connection.introspection.get_relations(cursor, table_name)
       
    48             except NotImplementedError:
       
    49                 relations = {}
       
    50             try:
       
    51                 indexes = connection.introspection.get_indexes(cursor, table_name)
       
    52             except NotImplementedError:
       
    53                 indexes = {}
       
    54             for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
       
    55                 column_name = row[0]
       
    56                 att_name = column_name.lower()
       
    57                 comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
       
    58                 extra_params = {}  # Holds Field parameters such as 'db_column'.
       
    59 
       
    60                 # If the column name can't be used verbatim as a Python
       
    61                 # attribute, set the "db_column" for this Field.
       
    62                 if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name:
       
    63                     extra_params['db_column'] = column_name
       
    64 
       
    65                 # Modify the field name to make it Python-compatible.
       
    66                 if ' ' in att_name:
       
    67                     att_name = att_name.replace(' ', '_')
       
    68                     comment_notes.append('Field renamed to remove spaces.')
       
    69                 if '-' in att_name:
       
    70                     att_name = att_name.replace('-', '_')
       
    71                     comment_notes.append('Field renamed to remove dashes.')
       
    72                 if keyword.iskeyword(att_name):
       
    73                     att_name += '_field'
       
    74                     comment_notes.append('Field renamed because it was a Python reserved word.')
       
    75                 if column_name != att_name:
       
    76                     comment_notes.append('Field name made lowercase.')
       
    77 
       
    78                 if i in relations:
       
    79                     rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
       
    80                     field_type = 'ForeignKey(%s' % rel_to
       
    81                     if att_name.endswith('_id'):
       
    82                         att_name = att_name[:-3]
       
    83                     else:
       
    84                         extra_params['db_column'] = column_name
       
    85                 else:
       
    86                     # Calling `get_field_type` to get the field type string and any
       
    87                     # additional paramters and notes.
       
    88                     field_type, field_params, field_notes = self.get_field_type(connection, table_name, row)
       
    89                     extra_params.update(field_params)
       
    90                     comment_notes.extend(field_notes)
       
    91 
       
    92                     # Add primary_key and unique, if necessary.
       
    93                     if column_name in indexes:
       
    94                         if indexes[column_name]['primary_key']:
       
    95                             extra_params['primary_key'] = True
       
    96                         elif indexes[column_name]['unique']:
       
    97                             extra_params['unique'] = True
       
    98 
       
    99                     field_type += '('
       
   100 
       
   101                 # Don't output 'id = meta.AutoField(primary_key=True)', because
       
   102                 # that's assumed if it doesn't exist.
       
   103                 if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
       
   104                     continue
       
   105 
       
   106                 # Add 'null' and 'blank', if the 'null_ok' flag was present in the
       
   107                 # table description.
       
   108                 if row[6]: # If it's NULL...
       
   109                     extra_params['blank'] = True
       
   110                     if not field_type in ('TextField(', 'CharField('):
       
   111                         extra_params['null'] = True
       
   112 
       
   113                 field_desc = '%s = models.%s' % (att_name, field_type)
       
   114                 if extra_params:
       
   115                     if not field_desc.endswith('('):
       
   116                         field_desc += ', '
       
   117                     field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()])
       
   118                 field_desc += ')'
       
   119                 if comment_notes:
       
   120                     field_desc += ' # ' + ' '.join(comment_notes)
       
   121                 yield '    %s' % field_desc
       
   122             for meta_line in self.get_meta(table_name):
       
   123                 yield meta_line
       
   124 
       
   125     def get_field_type(self, connection, table_name, row):
       
   126         """
       
   127         Given the database connection, the table name, and the cursor row
       
   128         description, this routine will return the given field type name, as
       
   129         well as any additional keyword parameters and notes for the field.
       
   130         """
       
   131         field_params = {}
       
   132         field_notes = []
       
   133 
       
   134         try:
       
   135             field_type = connection.introspection.get_field_type(row[1], row)
       
   136         except KeyError:
       
   137             field_type = 'TextField'
       
   138             field_notes.append('This field type is a guess.')
       
   139 
       
   140         # This is a hook for DATA_TYPES_REVERSE to return a tuple of
       
   141         # (field_type, field_params_dict).
       
   142         if type(field_type) is tuple:
       
   143             field_type, new_params = field_type
       
   144             field_params.update(new_params)
       
   145 
       
   146         # Add max_length for all CharFields.
       
   147         if field_type == 'CharField' and row[3]:
       
   148             field_params['max_length'] = row[3]
       
   149 
       
   150         if field_type == 'DecimalField':
       
   151             field_params['max_digits'] = row[4]
       
   152             field_params['decimal_places'] = row[5]
       
   153 
       
   154         return field_type, field_params, field_notes
       
   155 
       
   156     def get_meta(self, table_name):
       
   157         """
       
   158         Return a sequence comprising the lines of code necessary
       
   159         to construct the inner Meta class for the model corresponding
       
   160         to the given database table name.
       
   161         """
       
   162         return ['    class Meta:',
       
   163                 '        db_table = %r' % table_name,
       
   164                 '']