Ticket #12460: 12460-3.diff

File 12460-3.diff, 8.6 KB (added by Claude Paroz, 12 years ago)

Updated patch

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

    diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py
    index 8da0d7e..f87bd20 100644
    a b class Command(NoArgsCommand):  
    5151                indexes = connection.introspection.get_indexes(cursor, table_name)
    5252            except NotImplementedError:
    5353                indexes = {}
     54            used_column_names = [] # Holds column names used in the table so far
    5455            for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
    55                 column_name = row[0]
    56                 att_name = column_name.lower()
    5756                comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
    5857                extra_params = {}  # Holds Field parameters such as 'db_column'.
     58                column_name = row[0]
     59                is_relation = i in relations
     60
     61                att_name, params, notes = self.normalize_col_name(
     62                    column_name, used_column_names, is_relation)
     63                extra_params.update(params)
     64                comment_notes.extend(notes)
    5965
    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
     66                used_column_names.append(att_name)
    6467
    6568                # Add primary_key and unique, if necessary.
    6669                if column_name in indexes:
    class Command(NoArgsCommand):  
    6972                    elif indexes[column_name]['unique']:
    7073                        extra_params['unique'] = True
    7174
    72                 # Modify the field name to make it Python-compatible.
    73                 if ' ' in att_name:
    74                     att_name = att_name.replace(' ', '_')
    75                     comment_notes.append('Field renamed to remove spaces.')
    76 
    77                 if '-' in att_name:
    78                     att_name = att_name.replace('-', '_')
    79                     comment_notes.append('Field renamed to remove dashes.')
    80 
    81                 if column_name != att_name:
    82                     comment_notes.append('Field name made lowercase.')
    83 
    84                 if i in relations:
     75                if is_relation:
    8576                    rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
    8677                    field_type = 'ForeignKey(%s' % rel_to
    87                     if att_name.endswith('_id'):
    88                         att_name = att_name[:-3]
    89                     else:
    90                         extra_params['db_column'] = column_name
    9178                else:
    9279                    # Calling `get_field_type` to get the field type string and any
    9380                    # additional paramters and notes.
    class Command(NoArgsCommand):  
    9784
    9885                    field_type += '('
    9986
    100                 if keyword.iskeyword(att_name):
    101                     att_name += '_field'
    102                     comment_notes.append('Field renamed because it was a Python reserved word.')
    103 
    104                 if att_name[0].isdigit():
    105                     att_name = 'number_%s' % att_name
    106                     extra_params['db_column'] = unicode(column_name)
    107                     comment_notes.append("Field renamed because it wasn't a "
    108                         "valid Python identifier.")
    109 
    11087                # Don't output 'id = meta.AutoField(primary_key=True)', because
    11188                # that's assumed if it doesn't exist.
    11289                if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
    class Command(NoArgsCommand):  
    131108            for meta_line in self.get_meta(table_name):
    132109                yield meta_line
    133110
     111    def normalize_col_name(self, col_name, used_column_names, is_relation):
     112        """Modify the column name to make it Python-compatible as a field name"""
     113        field_params = {}
     114        field_notes = []
     115
     116        new_name = col_name.lower()
     117        if new_name != col_name:
     118            field_notes.append('Field name made lowercase.')
     119
     120        if is_relation:
     121            if new_name.endswith('_id'):
     122                new_name = new_name[:-3]
     123            else:
     124                field_params['db_column'] = col_name
     125
     126        if ' ' in new_name:
     127            new_name = new_name.replace(' ', '_')
     128            field_notes.append('Field renamed to remove spaces.')
     129
     130        if '-' in new_name:
     131            new_name = new_name.replace('-', '_')
     132            field_notes.append('Field renamed to remove dashes.')
     133
     134        if new_name.find('__') >= 0:
     135            while new_name.find('__') >= 0:
     136                new_name = new_name.replace('__', '_')
     137            field_notes.append("Field renamed because it contained more than one '_' in a row.")
     138
     139        if new_name.startswith('_'):
     140            new_name = 'field%s' % new_name
     141            field_notes.append("Field renamed because it started with '_'.")
     142
     143        if new_name.endswith('_'):
     144            new_name = '%sfield' % new_name
     145            field_notes.append("Field renamed because it ended with '_'.")
     146
     147        if keyword.iskeyword(new_name):
     148            new_name += '_field'
     149            field_notes.append('Field renamed because it was a Python reserved word.')
     150
     151        if new_name[0].isdigit():
     152            new_name = 'number_%s' % new_name
     153            field_notes.append("Field renamed because it wasn't a "
     154                "valid Python identifier.")
     155
     156        if new_name in used_column_names:
     157            num = 0
     158            while '%s_%d' % (new_name, num) in used_column_names:
     159                num += 1
     160            new_name = '%s_%d' % (new_name, num)
     161            field_notes.append('Field renamed because of name conflict.')
     162
     163        if col_name != new_name and field_notes:
     164            field_params['db_column'] = unicode(col_name)
     165
     166        return new_name, field_params, field_notes
     167
    134168    def get_field_type(self, connection, table_name, row):
    135169        """
    136170        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 944eb64..ea6cc2b 100644
    a b class InspectDBTestCase(TestCase):  
    1212        call_command('inspectdb', stdout=out)
    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)
    15         self.assertIn("from_field = models.ForeignKey(InspectdbPeople)", out.getvalue())
     15        self.assertIn("from_field = models.ForeignKey(InspectdbPeople, db_column=u'from_id')", out.getvalue())
    1616        self.assertIn("people_pk = models.ForeignKey(InspectdbPeople, primary_key=True)",
    1717            out.getvalue())
    1818        self.assertIn("people_unique = models.ForeignKey(InspectdbPeople, unique=True)",
    class InspectDBTestCase(TestCase):  
    3333
    3434        self.assertNotIn("    45extra = models.CharField", out.getvalue(), msg=error_message)
    3535        self.assertIn("number_45extra = models.CharField", out.getvalue())
     36
     37    def test_underscores_column_name_introspection(self):
     38        """Introspection of column names containing underscores (#12460)"""
     39        out = StringIO()
     40        call_command('inspectdb', stdout=out)
     41        self.assertIn("field = models.IntegerField()", out.getvalue())
     42        self.assertIn("field_field = models.IntegerField(db_column=u'Field_')", out.getvalue())
     43        self.assertIn("field_field_0 = models.IntegerField(db_column=u'Field__')", out.getvalue())
     44        self.assertIn("field_field_1 = models.IntegerField(db_column=u'__field')", out.getvalue())
Back to Top