|
1 import keyword |
|
2 from optparse import make_option |
|
3 |
1 from django.core.management.base import NoArgsCommand, CommandError |
4 from django.core.management.base import NoArgsCommand, CommandError |
|
5 from django.db import connections, DEFAULT_DB_ALIAS |
2 |
6 |
3 class Command(NoArgsCommand): |
7 class Command(NoArgsCommand): |
4 help = "Introspects the database tables in the given database and outputs a Django model module." |
8 help = "Introspects the database tables in the given database and outputs a Django model module." |
5 |
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 |
6 requires_model_validation = False |
16 requires_model_validation = False |
|
17 |
|
18 db_module = 'django.db' |
7 |
19 |
8 def handle_noargs(self, **options): |
20 def handle_noargs(self, **options): |
9 try: |
21 try: |
10 for line in self.handle_inspection(): |
22 for line in self.handle_inspection(options): |
11 print line |
23 print line |
12 except NotImplementedError: |
24 except NotImplementedError: |
13 raise CommandError("Database inspection isn't supported for the currently selected database backend.") |
25 raise CommandError("Database inspection isn't supported for the currently selected database backend.") |
14 |
26 |
15 def handle_inspection(self): |
27 def handle_inspection(self, options): |
16 from django.db import connection |
28 connection = connections[options.get('database', DEFAULT_DB_ALIAS)] |
17 import keyword |
|
18 |
29 |
19 table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') |
30 table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') |
20 |
31 |
21 cursor = connection.cursor() |
32 cursor = connection.cursor() |
22 yield "# This is an auto-generated Django model module." |
33 yield "# This is an auto-generated Django model module." |
26 yield "# Feel free to rename the models, but don't rename db_table values or field names." |
37 yield "# Feel free to rename the models, but don't rename db_table values or field names." |
27 yield "#" |
38 yield "#" |
28 yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'" |
39 yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'" |
29 yield "# into your database." |
40 yield "# into your database." |
30 yield '' |
41 yield '' |
31 yield 'from django.db import models' |
42 yield 'from %s import models' % self.db_module |
32 yield '' |
43 yield '' |
33 for table_name in connection.introspection.get_table_list(cursor): |
44 for table_name in connection.introspection.get_table_list(cursor): |
34 yield 'class %s(models.Model):' % table2model(table_name) |
45 yield 'class %s(models.Model):' % table2model(table_name) |
35 try: |
46 try: |
36 relations = connection.introspection.get_relations(cursor, table_name) |
47 relations = connection.introspection.get_relations(cursor, table_name) |
70 if att_name.endswith('_id'): |
81 if att_name.endswith('_id'): |
71 att_name = att_name[:-3] |
82 att_name = att_name[:-3] |
72 else: |
83 else: |
73 extra_params['db_column'] = column_name |
84 extra_params['db_column'] = column_name |
74 else: |
85 else: |
75 try: |
86 # Calling `get_field_type` to get the field type string and any |
76 field_type = connection.introspection.get_field_type(row[1], row) |
87 # additional paramters and notes. |
77 except KeyError: |
88 field_type, field_params, field_notes = self.get_field_type(connection, table_name, row) |
78 field_type = 'TextField' |
89 extra_params.update(field_params) |
79 comment_notes.append('This field type is a guess.') |
90 comment_notes.extend(field_notes) |
80 |
|
81 # This is a hook for DATA_TYPES_REVERSE to return a tuple of |
|
82 # (field_type, extra_params_dict). |
|
83 if type(field_type) is tuple: |
|
84 field_type, new_params = field_type |
|
85 extra_params.update(new_params) |
|
86 |
|
87 # Add max_length for all CharFields. |
|
88 if field_type == 'CharField' and row[3]: |
|
89 extra_params['max_length'] = row[3] |
|
90 |
|
91 if field_type == 'DecimalField': |
|
92 extra_params['max_digits'] = row[4] |
|
93 extra_params['decimal_places'] = row[5] |
|
94 |
91 |
95 # Add primary_key and unique, if necessary. |
92 # Add primary_key and unique, if necessary. |
96 if column_name in indexes: |
93 if column_name in indexes: |
97 if indexes[column_name]['primary_key']: |
94 if indexes[column_name]['primary_key']: |
98 extra_params['primary_key'] = True |
95 extra_params['primary_key'] = True |
120 field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()]) |
117 field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()]) |
121 field_desc += ')' |
118 field_desc += ')' |
122 if comment_notes: |
119 if comment_notes: |
123 field_desc += ' # ' + ' '.join(comment_notes) |
120 field_desc += ' # ' + ' '.join(comment_notes) |
124 yield ' %s' % field_desc |
121 yield ' %s' % field_desc |
125 yield ' class Meta:' |
122 for meta_line in self.get_meta(table_name): |
126 yield ' db_table = %r' % table_name |
123 yield meta_line |
127 yield '' |
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 ''] |