Fixed #27533 -- Fixed inspectdb crash if a unique constraint uses an unsupported type.

This commit is contained in:
Michael Sinov 2016-12-13 21:38:09 +10:00 committed by Tim Graham
parent a170dac887
commit 9aca67bea8
4 changed files with 40 additions and 6 deletions

View File

@ -567,6 +567,7 @@ answer newbie questions, and generally made Django that much better:
Michael Placentra II <someone@michaelplacentra2.net> Michael Placentra II <someone@michaelplacentra2.net>
Michael Radziej <mir@noris.de> Michael Radziej <mir@noris.de>
Michael Schwarz <michi.schwarz@gmail.com> Michael Schwarz <michi.schwarz@gmail.com>
Michael Sinov <sihaelov@gmail.com>
Michael Thornhill <michael.thornhill@gmail.com> Michael Thornhill <michael.thornhill@gmail.com>
Michal Chruszcz <troll@pld-linux.org> Michal Chruszcz <troll@pld-linux.org>
michal@plovarna.cz michal@plovarna.cz

View File

@ -269,16 +269,24 @@ class Command(BaseCommand):
to the given database table name. to the given database table name.
""" """
unique_together = [] unique_together = []
has_unsupported_constraint = False
for params in constraints.values(): for params in constraints.values():
if params['unique']: if params['unique']:
columns = params['columns'] columns = params['columns']
if None in columns:
has_unsupported_constraint = True
columns = [x for x in columns if x is not None]
if len(columns) > 1: if len(columns) > 1:
unique_together.append(str(tuple(column_to_field_name[c] for c in columns))) unique_together.append(str(tuple(column_to_field_name[c] for c in columns)))
managed_comment = " # Created from a view. Don't remove." if is_view else "" managed_comment = " # Created from a view. Don't remove." if is_view else ""
meta = ["", meta = ['']
" class Meta:", if has_unsupported_constraint:
" managed = False%s" % managed_comment, meta.append(' # A unique constraint could not be introspected.')
" db_table = '%s'" % table_name] meta += [
' class Meta:',
' managed = False%s' % managed_comment,
' db_table = %r' % table_name
]
if unique_together: if unique_together:
tup = '(' + ', '.join(unique_together) + ',)' tup = '(' + ', '.join(unique_together) + ',)'
meta += [" unique_together = %s" % tup] meta += [" unique_together = %s" % tup]

View File

@ -17,6 +17,7 @@ class PeopleData(models.Model):
class PeopleMoreData(models.Model): class PeopleMoreData(models.Model):
people_unique = models.ForeignKey(People, models.CASCADE, unique=True) people_unique = models.ForeignKey(People, models.CASCADE, unique=True)
message = models.ForeignKey(Message, models.CASCADE, blank=True, null=True)
license = models.CharField(max_length=255) license = models.CharField(max_length=255)

View File

@ -7,6 +7,8 @@ from django.db import connection
from django.db.backends.base.introspection import TableInfo from django.db.backends.base.introspection import TableInfo
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
from .models import PeopleMoreData
def inspectdb_tables_only(table_name): def inspectdb_tables_only(table_name):
""" """
@ -21,6 +23,7 @@ def special_table_only(table_name):
class InspectDBTestCase(TestCase): class InspectDBTestCase(TestCase):
unique_re = re.compile(r'.*unique_together = \((.+),\).*')
def test_stealth_table_name_filter_option(self): def test_stealth_table_name_filter_option(self):
out = StringIO() out = StringIO()
@ -212,8 +215,7 @@ class InspectDBTestCase(TestCase):
call_command('inspectdb', 'inspectdb_uniquetogether', stdout=out) call_command('inspectdb', 'inspectdb_uniquetogether', stdout=out)
output = out.getvalue() output = out.getvalue()
self.assertIn(" unique_together = (('", output) self.assertIn(" unique_together = (('", output)
unique_re = re.compile(r'.*unique_together = \((.+),\).*') unique_together_match = re.findall(self.unique_re, output)
unique_together_match = re.findall(unique_re, output)
# There should be one unique_together tuple. # There should be one unique_together tuple.
self.assertEqual(len(unique_together_match), 1) self.assertEqual(len(unique_together_match), 1)
fields = unique_together_match[0] fields = unique_together_match[0]
@ -225,6 +227,28 @@ class InspectDBTestCase(TestCase):
# are given an integer suffix. # are given an integer suffix.
self.assertIn("('non_unique_column', 'non_unique_column_0')", fields) self.assertIn("('non_unique_column', 'non_unique_column_0')", fields)
@skipUnless(connection.vendor == 'postgresql', 'PostgreSQL specific SQL')
def test_unsupported_unique_together(self):
"""Unsupported index types (COALESCE here) are skipped."""
with connection.cursor() as c:
c.execute(
'CREATE UNIQUE INDEX Findex ON %s '
'(id, people_unique_id, COALESCE(message_id, -1))' % PeopleMoreData._meta.db_table
)
try:
out = StringIO()
call_command(
'inspectdb',
table_name_filter=lambda tn: tn.startswith(PeopleMoreData._meta.db_table),
stdout=out,
)
output = out.getvalue()
self.assertIn('# A unique constraint could not be introspected.', output)
self.assertEqual(re.findall(self.unique_re, output), ["('id', 'people_unique')"])
finally:
with connection.cursor() as c:
c.execute('DROP INDEX Findex')
@skipUnless(connection.vendor == 'sqlite', @skipUnless(connection.vendor == 'sqlite',
"Only patched sqlite's DatabaseIntrospection.data_types_reverse for this test") "Only patched sqlite's DatabaseIntrospection.data_types_reverse for this test")
def test_custom_fields(self): def test_custom_fields(self):