|
1 from django.core.management.base import NoArgsCommand, CommandError |
|
2 |
|
3 class Command(NoArgsCommand): |
|
4 help = "Introspects the database tables in the given database and outputs a Django model module." |
|
5 |
|
6 requires_model_validation = False |
|
7 |
|
8 def handle_noargs(self, **options): |
|
9 try: |
|
10 for line in self.handle_inspection(): |
|
11 print line |
|
12 except NotImplementedError: |
|
13 raise CommandError("Database inspection isn't supported for the currently selected database backend.") |
|
14 |
|
15 def handle_inspection(self): |
|
16 from django.db import connection |
|
17 import keyword |
|
18 |
|
19 table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') |
|
20 |
|
21 cursor = connection.cursor() |
|
22 yield "# This is an auto-generated Django model module." |
|
23 yield "# You'll have to do the following manually to clean this up:" |
|
24 yield "# * Rearrange models' order" |
|
25 yield "# * Make sure each model has one field with primary_key=True" |
|
26 yield "# Feel free to rename the models, but don't rename db_table values or field names." |
|
27 yield "#" |
|
28 yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'" |
|
29 yield "# into your database." |
|
30 yield '' |
|
31 yield 'from django.db import models' |
|
32 yield '' |
|
33 for table_name in connection.introspection.get_table_list(cursor): |
|
34 yield 'class %s(models.Model):' % table2model(table_name) |
|
35 try: |
|
36 relations = connection.introspection.get_relations(cursor, table_name) |
|
37 except NotImplementedError: |
|
38 relations = {} |
|
39 try: |
|
40 indexes = connection.introspection.get_indexes(cursor, table_name) |
|
41 except NotImplementedError: |
|
42 indexes = {} |
|
43 for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)): |
|
44 column_name = row[0] |
|
45 att_name = column_name.lower() |
|
46 comment_notes = [] # Holds Field notes, to be displayed in a Python comment. |
|
47 extra_params = {} # Holds Field parameters such as 'db_column'. |
|
48 |
|
49 # If the column name can't be used verbatim as a Python |
|
50 # attribute, set the "db_column" for this Field. |
|
51 if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name: |
|
52 extra_params['db_column'] = column_name |
|
53 |
|
54 # Modify the field name to make it Python-compatible. |
|
55 if ' ' in att_name: |
|
56 att_name = att_name.replace(' ', '_') |
|
57 comment_notes.append('Field renamed to remove spaces.') |
|
58 if '-' in att_name: |
|
59 att_name = att_name.replace('-', '_') |
|
60 comment_notes.append('Field renamed to remove dashes.') |
|
61 if keyword.iskeyword(att_name): |
|
62 att_name += '_field' |
|
63 comment_notes.append('Field renamed because it was a Python reserved word.') |
|
64 if column_name != att_name: |
|
65 comment_notes.append('Field name made lowercase.') |
|
66 |
|
67 if i in relations: |
|
68 rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1]) |
|
69 field_type = 'ForeignKey(%s' % rel_to |
|
70 if att_name.endswith('_id'): |
|
71 att_name = att_name[:-3] |
|
72 else: |
|
73 extra_params['db_column'] = column_name |
|
74 else: |
|
75 try: |
|
76 field_type = connection.introspection.get_field_type(row[1], row) |
|
77 except KeyError: |
|
78 field_type = 'TextField' |
|
79 comment_notes.append('This field type is a guess.') |
|
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 |
|
95 # Add primary_key and unique, if necessary. |
|
96 if column_name in indexes: |
|
97 if indexes[column_name]['primary_key']: |
|
98 extra_params['primary_key'] = True |
|
99 elif indexes[column_name]['unique']: |
|
100 extra_params['unique'] = True |
|
101 |
|
102 field_type += '(' |
|
103 |
|
104 # Don't output 'id = meta.AutoField(primary_key=True)', because |
|
105 # that's assumed if it doesn't exist. |
|
106 if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}: |
|
107 continue |
|
108 |
|
109 # Add 'null' and 'blank', if the 'null_ok' flag was present in the |
|
110 # table description. |
|
111 if row[6]: # If it's NULL... |
|
112 extra_params['blank'] = True |
|
113 if not field_type in ('TextField(', 'CharField('): |
|
114 extra_params['null'] = True |
|
115 |
|
116 field_desc = '%s = models.%s' % (att_name, field_type) |
|
117 if extra_params: |
|
118 if not field_desc.endswith('('): |
|
119 field_desc += ', ' |
|
120 field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()]) |
|
121 field_desc += ')' |
|
122 if comment_notes: |
|
123 field_desc += ' # ' + ' '.join(comment_notes) |
|
124 yield ' %s' % field_desc |
|
125 yield ' class Meta:' |
|
126 yield ' db_table = %r' % table_name |
|
127 yield '' |