Ticket #12460: 12460-4.diff

File 12460-4.diff, 8.8 KB (added by Claude Paroz, 13 years ago)

Patch updated to current trunk

  • django/core/management/commands/inspectdb.py

    diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py
    index af2da45..54b35f2 100644
    a b class Command(NoArgsCommand):  
    5353                indexes = connection.introspection.get_indexes(cursor, table_name)
    5454            except NotImplementedError:
    5555                indexes = {}
     56            used_column_names = [] # Holds column names used in the table so far
    5657            for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
    57                 column_name = row[0]
    58                 att_name = column_name.lower()
    5958                comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
    6059                extra_params = {}  # Holds Field parameters such as 'db_column'.
     60                column_name = row[0]
     61                is_relation = i in relations
    6162
    62                 # If the column name can't be used verbatim as a Python
    63                 # attribute, set the "db_column" for this Field.
    64                 if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name:
    65                     extra_params['db_column'] = column_name
     63                att_name, params, notes = self.normalize_col_name(
     64                    column_name, used_column_names, is_relation)
     65                extra_params.update(params)
     66                comment_notes.extend(notes)
     67
     68                used_column_names.append(att_name)
    6669
    6770                # Add primary_key and unique, if necessary.
    6871                if column_name in indexes:
    class Command(NoArgsCommand):  
    7174                    elif indexes[column_name]['unique']:
    7275                        extra_params['unique'] = True
    7376
    74                 # Modify the field name to make it Python-compatible.
    75                 if ' ' in att_name:
    76                     att_name = att_name.replace(' ', '_')
    77                     comment_notes.append('Field renamed to remove spaces.')
    78 
    79                 if '-' in att_name:
    80                     att_name = att_name.replace('-', '_')
    81                     comment_notes.append('Field renamed to remove dashes.')
    82 
    83                 if column_name != att_name:
    84                     comment_notes.append('Field name made lowercase.')
    85 
    86                 if i in relations:
     77                if is_relation:
    8778                    rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
    88 
    8979                    if rel_to in known_models:
    9080                        field_type = 'ForeignKey(%s' % rel_to
    9181                    else:
    9282                        field_type = "ForeignKey('%s'" % rel_to
    93 
    94                     if att_name.endswith('_id'):
    95                         att_name = att_name[:-3]
    96                     else:
    97                         extra_params['db_column'] = column_name
    9883                else:
    9984                    # Calling `get_field_type` to get the field type string and any
    10085                    # additional paramters and notes.
    class Command(NoArgsCommand):  
    10489
    10590                    field_type += '('
    10691
    107                 if keyword.iskeyword(att_name):
    108                     att_name += '_field'
    109                     comment_notes.append('Field renamed because it was a Python reserved word.')
    110 
    111                 if att_name[0].isdigit():
    112                     att_name = 'number_%s' % att_name
    113                     extra_params['db_column'] = unicode(column_name)
    114                     comment_notes.append("Field renamed because it wasn't a "
    115                         "valid Python identifier.")
    116 
    11792                # Don't output 'id = meta.AutoField(primary_key=True)', because
    11893                # that's assumed if it doesn't exist.
    11994                if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
    class Command(NoArgsCommand):  
    138113            for meta_line in self.get_meta(table_name):
    139114                yield meta_line
    140115
     116    def normalize_col_name(self, col_name, used_column_names, is_relation):
     117        """Modify the column name to make it Python-compatible as a field name"""
     118        field_params = {}
     119        field_notes = []
     120
     121        new_name = col_name.lower()
     122        if new_name != col_name:
     123            field_notes.append('Field name made lowercase.')
     124
     125        if is_relation:
     126            if new_name.endswith('_id'):
     127                new_name = new_name[:-3]
     128            else:
     129                field_params['db_column'] = col_name
     130
     131        if ' ' in new_name:
     132            new_name = new_name.replace(' ', '_')
     133            field_notes.append('Field renamed to remove spaces.')
     134
     135        if '-' in new_name:
     136            new_name = new_name.replace('-', '_')
     137            field_notes.append('Field renamed to remove dashes.')
     138
     139        if new_name.find('__') >= 0:
     140            while new_name.find('__') >= 0:
     141                new_name = new_name.replace('__', '_')
     142            field_notes.append("Field renamed because it contained more than one '_' in a row.")
     143
     144        if new_name.startswith('_'):
     145            new_name = 'field%s' % new_name
     146            field_notes.append("Field renamed because it started with '_'.")
     147
     148        if new_name.endswith('_'):
     149            new_name = '%sfield' % new_name
     150            field_notes.append("Field renamed because it ended with '_'.")
     151
     152        if keyword.iskeyword(new_name):
     153            new_name += '_field'
     154            field_notes.append('Field renamed because it was a Python reserved word.')
     155
     156        if new_name[0].isdigit():
     157            new_name = 'number_%s' % new_name
     158            field_notes.append("Field renamed because it wasn't a "
     159                "valid Python identifier.")
     160
     161        if new_name in used_column_names:
     162            num = 0
     163            while '%s_%d' % (new_name, num) in used_column_names:
     164                num += 1
     165            new_name = '%s_%d' % (new_name, num)
     166            field_notes.append('Field renamed because of name conflict.')
     167
     168        if col_name != new_name and field_notes:
     169            field_params['db_column'] = unicode(col_name)
     170
     171        return new_name, field_params, field_notes
     172
    141173    def get_field_type(self, connection, table_name, row):
    142174        """
    143175        Given the database connection, the table name, and the cursor row
  • tests/regressiontests/inspectdb/models.py

    diff --git a/tests/regressiontests/inspectdb/models.py b/tests/regressiontests/inspectdb/models.py
    index 9f81585..352053a 100644
    a b class DigitsInColumnName(models.Model):  
    1919    all_digits = models.CharField(max_length=11, db_column='123')
    2020    leading_digit = models.CharField(max_length=11, db_column='4extra')
    2121    leading_digits = models.CharField(max_length=11, db_column='45extra')
     22
     23class UnderscoresInColumnName(models.Model):
     24    field = models.IntegerField(db_column='field')
     25    field_field_0 = models.IntegerField(db_column='Field_')
     26    field_field_1 = models.IntegerField(db_column='Field__')
     27    field_field_2 = models.IntegerField(db_column='__field')
  • tests/regressiontests/inspectdb/tests.py

    diff --git a/tests/regressiontests/inspectdb/tests.py b/tests/regressiontests/inspectdb/tests.py
    index 6896bf9..41dfbb4 100644
    a b class InspectDBTestCase(TestCase):  
    1313        error_message = "inspectdb generated an attribute name which is a python keyword"
    1414        self.assertNotIn("from = models.ForeignKey(InspectdbPeople)", out.getvalue(), msg=error_message)
    1515        # As InspectdbPeople model is defined after InspectdbMessage, it should be quoted
    16         self.assertIn("from_field = models.ForeignKey('InspectdbPeople')", out.getvalue())
     16        self.assertIn("from_field = models.ForeignKey('InspectdbPeople', db_column=u'from_id')", out.getvalue())
    1717        self.assertIn("people_pk = models.ForeignKey(InspectdbPeople, primary_key=True)",
    1818            out.getvalue())
    1919        self.assertIn("people_unique = models.ForeignKey(InspectdbPeople, unique=True)",
    class InspectDBTestCase(TestCase):  
    3434
    3535        self.assertNotIn("    45extra = models.CharField", out.getvalue(), msg=error_message)
    3636        self.assertIn("number_45extra = models.CharField", out.getvalue())
     37
     38    def test_underscores_column_name_introspection(self):
     39        """Introspection of column names containing underscores (#12460)"""
     40        out = StringIO()
     41        call_command('inspectdb', stdout=out)
     42        self.assertIn("field = models.IntegerField()", out.getvalue())
     43        self.assertIn("field_field = models.IntegerField(db_column=u'Field_')", out.getvalue())
     44        self.assertIn("field_field_0 = models.IntegerField(db_column=u'Field__')", out.getvalue())
     45        self.assertIn("field_field_1 = models.IntegerField(db_column=u'__field')", out.getvalue())
Back to Top