Refs #29641 -- Refactored database schema constraint creation.
Added a test for constraint names in the database. Updated SQLite introspection to use sqlparse to allow reading the constraint name for table check and unique constraints. Co-authored-by: Ian Foote <python@ian.feete.org>
This commit is contained in:
parent
2f120ac517
commit
dba4a634ba
|
@ -61,25 +61,24 @@ class BaseDatabaseSchemaEditor:
|
||||||
sql_rename_column = "ALTER TABLE %(table)s RENAME COLUMN %(old_column)s TO %(new_column)s"
|
sql_rename_column = "ALTER TABLE %(table)s RENAME COLUMN %(old_column)s TO %(new_column)s"
|
||||||
sql_update_with_default = "UPDATE %(table)s SET %(column)s = %(default)s WHERE %(column)s IS NULL"
|
sql_update_with_default = "UPDATE %(table)s SET %(column)s = %(default)s WHERE %(column)s IS NULL"
|
||||||
|
|
||||||
sql_check = "CONSTRAINT %(name)s CHECK (%(check)s)"
|
sql_foreign_key_constraint = "FOREIGN KEY (%(column)s) REFERENCES %(to_table)s (%(to_column)s)%(deferrable)s"
|
||||||
sql_create_check = "ALTER TABLE %(table)s ADD %(check)s"
|
sql_unique_constraint = "UNIQUE (%(columns)s)"
|
||||||
sql_delete_check = "ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
|
sql_check_constraint = "CHECK (%(check)s)"
|
||||||
|
sql_create_constraint = "ALTER TABLE %(table)s ADD %(constraint)s"
|
||||||
|
sql_delete_constraint = "ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
|
||||||
|
sql_constraint = "CONSTRAINT %(name)s %(constraint)s"
|
||||||
|
|
||||||
sql_create_unique = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s UNIQUE (%(columns)s)"
|
sql_create_unique = None
|
||||||
sql_delete_unique = "ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
|
sql_delete_unique = sql_delete_constraint
|
||||||
|
|
||||||
sql_create_fk = (
|
|
||||||
"ALTER TABLE %(table)s ADD CONSTRAINT %(name)s FOREIGN KEY (%(column)s) "
|
|
||||||
"REFERENCES %(to_table)s (%(to_column)s)%(deferrable)s"
|
|
||||||
)
|
|
||||||
sql_create_inline_fk = None
|
sql_create_inline_fk = None
|
||||||
sql_delete_fk = "ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
|
sql_delete_fk = sql_delete_constraint
|
||||||
|
|
||||||
sql_create_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s)%(extra)s%(condition)s"
|
sql_create_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s)%(extra)s%(condition)s"
|
||||||
sql_delete_index = "DROP INDEX %(name)s"
|
sql_delete_index = "DROP INDEX %(name)s"
|
||||||
|
|
||||||
sql_create_pk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s PRIMARY KEY (%(columns)s)"
|
sql_create_pk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s PRIMARY KEY (%(columns)s)"
|
||||||
sql_delete_pk = "ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
|
sql_delete_pk = sql_delete_constraint
|
||||||
|
|
||||||
sql_delete_procedure = 'DROP PROCEDURE %(procedure)s'
|
sql_delete_procedure = 'DROP PROCEDURE %(procedure)s'
|
||||||
|
|
||||||
|
@ -254,7 +253,7 @@ class BaseDatabaseSchemaEditor:
|
||||||
# Check constraints can go on the column SQL here
|
# Check constraints can go on the column SQL here
|
||||||
db_params = field.db_parameters(connection=self.connection)
|
db_params = field.db_parameters(connection=self.connection)
|
||||||
if db_params['check']:
|
if db_params['check']:
|
||||||
definition += " CHECK (%s)" % db_params['check']
|
definition += " " + self.sql_check_constraint % db_params
|
||||||
# Autoincrement SQL (for backends with inline variant)
|
# Autoincrement SQL (for backends with inline variant)
|
||||||
col_type_suffix = field.db_type_suffix(connection=self.connection)
|
col_type_suffix = field.db_type_suffix(connection=self.connection)
|
||||||
if col_type_suffix:
|
if col_type_suffix:
|
||||||
|
@ -287,7 +286,7 @@ class BaseDatabaseSchemaEditor:
|
||||||
for fields in model._meta.unique_together:
|
for fields in model._meta.unique_together:
|
||||||
columns = [model._meta.get_field(field).column for field in fields]
|
columns = [model._meta.get_field(field).column for field in fields]
|
||||||
self.deferred_sql.append(self._create_unique_sql(model, columns))
|
self.deferred_sql.append(self._create_unique_sql(model, columns))
|
||||||
constraints = [check.constraint_sql(model, self) for check in model._meta.constraints]
|
constraints = [check.full_constraint_sql(model, self) for check in model._meta.constraints]
|
||||||
# Make the table
|
# Make the table
|
||||||
sql = self.sql_create_table % {
|
sql = self.sql_create_table % {
|
||||||
"table": self.quote_name(model._meta.db_table),
|
"table": self.quote_name(model._meta.db_table),
|
||||||
|
@ -596,7 +595,7 @@ class BaseDatabaseSchemaEditor:
|
||||||
old_field.column,
|
old_field.column,
|
||||||
))
|
))
|
||||||
for constraint_name in constraint_names:
|
for constraint_name in constraint_names:
|
||||||
self.execute(self._delete_constraint_sql(self.sql_delete_check, model, constraint_name))
|
self.execute(self._delete_constraint_sql(self.sql_delete_constraint, model, constraint_name))
|
||||||
# Have they renamed the column?
|
# Have they renamed the column?
|
||||||
if old_field.column != new_field.column:
|
if old_field.column != new_field.column:
|
||||||
self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
|
self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
|
||||||
|
@ -746,15 +745,16 @@ class BaseDatabaseSchemaEditor:
|
||||||
self.execute(self._create_fk_sql(rel.related_model, rel.field, "_fk"))
|
self.execute(self._create_fk_sql(rel.related_model, rel.field, "_fk"))
|
||||||
# Does it have check constraints we need to add?
|
# Does it have check constraints we need to add?
|
||||||
if old_db_params['check'] != new_db_params['check'] and new_db_params['check']:
|
if old_db_params['check'] != new_db_params['check'] and new_db_params['check']:
|
||||||
|
constraint = self.sql_constraint % {
|
||||||
|
'name': self.quote_name(
|
||||||
|
self._create_index_name(model._meta.db_table, [new_field.column], suffix='_check'),
|
||||||
|
),
|
||||||
|
'constraint': self.sql_check_constraint % new_db_params,
|
||||||
|
}
|
||||||
self.execute(
|
self.execute(
|
||||||
self.sql_create_check % {
|
self.sql_create_constraint % {
|
||||||
"table": self.quote_name(model._meta.db_table),
|
'table': self.quote_name(model._meta.db_table),
|
||||||
"check": self.sql_check % {
|
'constraint': constraint,
|
||||||
'name': self.quote_name(
|
|
||||||
self._create_index_name(model._meta.db_table, [new_field.column], suffix='_check'),
|
|
||||||
),
|
|
||||||
'check': new_db_params['check'],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# Drop the default if we need to
|
# Drop the default if we need to
|
||||||
|
@ -983,35 +983,57 @@ class BaseDatabaseSchemaEditor:
|
||||||
"type": new_type,
|
"type": new_type,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _create_fk_sql(self, model, field, suffix):
|
def _create_constraint_sql(self, table, name, constraint):
|
||||||
from_table = model._meta.db_table
|
constraint = Statement(self.sql_constraint, name=name, constraint=constraint)
|
||||||
from_column = field.column
|
return Statement(self.sql_create_constraint, table=table, constraint=constraint)
|
||||||
_, to_table = split_identifier(field.target_field.model._meta.db_table)
|
|
||||||
to_column = field.target_field.column
|
|
||||||
|
|
||||||
|
def _create_fk_sql(self, model, field, suffix):
|
||||||
def create_fk_name(*args, **kwargs):
|
def create_fk_name(*args, **kwargs):
|
||||||
return self.quote_name(self._create_index_name(*args, **kwargs))
|
return self.quote_name(self._create_index_name(*args, **kwargs))
|
||||||
|
|
||||||
return Statement(
|
table = Table(model._meta.db_table, self.quote_name)
|
||||||
self.sql_create_fk,
|
name = ForeignKeyName(
|
||||||
table=Table(from_table, self.quote_name),
|
model._meta.db_table,
|
||||||
name=ForeignKeyName(from_table, [from_column], to_table, [to_column], suffix, create_fk_name),
|
[field.column],
|
||||||
column=Columns(from_table, [from_column], self.quote_name),
|
split_identifier(field.target_field.model._meta.db_table)[1],
|
||||||
to_table=Table(field.target_field.model._meta.db_table, self.quote_name),
|
[field.target_field.column],
|
||||||
to_column=Columns(field.target_field.model._meta.db_table, [to_column], self.quote_name),
|
suffix,
|
||||||
deferrable=self.connection.ops.deferrable_sql(),
|
create_fk_name,
|
||||||
)
|
)
|
||||||
|
column = Columns(model._meta.db_table, [field.column], self.quote_name)
|
||||||
|
to_table = Table(field.target_field.model._meta.db_table, self.quote_name)
|
||||||
|
to_column = Columns(field.target_field.model._meta.db_table, [field.target_field.column], self.quote_name)
|
||||||
|
deferrable = self.connection.ops.deferrable_sql()
|
||||||
|
constraint = Statement(
|
||||||
|
self.sql_foreign_key_constraint,
|
||||||
|
column=column,
|
||||||
|
to_table=to_table,
|
||||||
|
to_column=to_column,
|
||||||
|
deferrable=deferrable,
|
||||||
|
)
|
||||||
|
return self._create_constraint_sql(table, name, constraint)
|
||||||
|
|
||||||
def _create_unique_sql(self, model, columns):
|
def _create_unique_sql(self, model, columns, name=None):
|
||||||
def create_unique_name(*args, **kwargs):
|
def create_unique_name(*args, **kwargs):
|
||||||
return self.quote_name(self._create_index_name(*args, **kwargs))
|
return self.quote_name(self._create_index_name(*args, **kwargs))
|
||||||
table = model._meta.db_table
|
|
||||||
return Statement(
|
table = Table(model._meta.db_table, self.quote_name)
|
||||||
self.sql_create_unique,
|
if name is None:
|
||||||
table=Table(table, self.quote_name),
|
name = IndexName(model._meta.db_table, columns, '_uniq', create_unique_name)
|
||||||
name=IndexName(table, columns, '_uniq', create_unique_name),
|
else:
|
||||||
columns=Columns(table, columns, self.quote_name),
|
name = self.quote_name(name)
|
||||||
)
|
columns = Columns(table, columns, self.quote_name)
|
||||||
|
if self.sql_create_unique:
|
||||||
|
# Some databases use a different syntax for unique constraint
|
||||||
|
# creation.
|
||||||
|
return Statement(
|
||||||
|
self.sql_create_unique,
|
||||||
|
table=table,
|
||||||
|
name=name,
|
||||||
|
columns=columns,
|
||||||
|
)
|
||||||
|
constraint = Statement(self.sql_unique_constraint, columns=columns)
|
||||||
|
return self._create_constraint_sql(table, name, constraint)
|
||||||
|
|
||||||
def _delete_constraint_sql(self, template, model, name):
|
def _delete_constraint_sql(self, template, model, name):
|
||||||
return template % {
|
return template % {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import sqlparse
|
||||||
|
|
||||||
from django.db.backends.base.introspection import (
|
from django.db.backends.base.introspection import (
|
||||||
BaseDatabaseIntrospection, FieldInfo, TableInfo,
|
BaseDatabaseIntrospection, FieldInfo, TableInfo,
|
||||||
)
|
)
|
||||||
|
@ -242,21 +244,39 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
# table_name is a view.
|
# table_name is a view.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
fields_with_check_constraints = [
|
# Check constraint parsing is based of SQLite syntax diagram.
|
||||||
schema_row.strip().split(' ')[0][1:-1]
|
# https://www.sqlite.org/syntaxdiagrams.html#table-constraint
|
||||||
for schema_row in table_schema.split(',')
|
def next_ttype(ttype):
|
||||||
if schema_row.find('CHECK') >= 0
|
for token in tokens:
|
||||||
]
|
if token.ttype == ttype:
|
||||||
for field_name in fields_with_check_constraints:
|
return token
|
||||||
# An arbitrary made up name.
|
|
||||||
constraints['__check__%s' % field_name] = {
|
statement = sqlparse.parse(table_schema)[0]
|
||||||
'columns': [field_name],
|
tokens = statement.flatten()
|
||||||
'primary_key': False,
|
for token in tokens:
|
||||||
'unique': False,
|
name = None
|
||||||
'foreign_key': False,
|
if token.match(sqlparse.tokens.Keyword, 'CONSTRAINT'):
|
||||||
'check': True,
|
# Table constraint
|
||||||
'index': False,
|
name_token = next_ttype(sqlparse.tokens.Literal.String.Symbol)
|
||||||
}
|
name = name_token.value[1:-1]
|
||||||
|
token = next_ttype(sqlparse.tokens.Keyword)
|
||||||
|
if token.match(sqlparse.tokens.Keyword, 'CHECK'):
|
||||||
|
# Column check constraint
|
||||||
|
if name is None:
|
||||||
|
column_token = next_ttype(sqlparse.tokens.Literal.String.Symbol)
|
||||||
|
column = column_token.value[1:-1]
|
||||||
|
name = '__check__%s' % column
|
||||||
|
columns = [column]
|
||||||
|
else:
|
||||||
|
columns = []
|
||||||
|
constraints[name] = {
|
||||||
|
'check': True,
|
||||||
|
'columns': columns,
|
||||||
|
'primary_key': False,
|
||||||
|
'unique': False,
|
||||||
|
'foreign_key': False,
|
||||||
|
'index': False,
|
||||||
|
}
|
||||||
# Get the index info
|
# Get the index info
|
||||||
cursor.execute("PRAGMA index_list(%s)" % self.connection.ops.quote_name(table_name))
|
cursor.execute("PRAGMA index_list(%s)" % self.connection.ops.quote_name(table_name))
|
||||||
for row in cursor.fetchall():
|
for row in cursor.fetchall():
|
||||||
|
|
|
@ -12,10 +12,10 @@ from django.db.utils import NotSupportedError
|
||||||
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
|
|
||||||
sql_delete_table = "DROP TABLE %(table)s"
|
sql_delete_table = "DROP TABLE %(table)s"
|
||||||
sql_create_fk = None
|
|
||||||
sql_create_inline_fk = "REFERENCES %(to_table)s (%(to_column)s) DEFERRABLE INITIALLY DEFERRED"
|
sql_create_inline_fk = "REFERENCES %(to_table)s (%(to_column)s) DEFERRABLE INITIALLY DEFERRED"
|
||||||
sql_create_unique = "CREATE UNIQUE INDEX %(name)s ON %(table)s (%(columns)s)"
|
sql_create_unique = "CREATE UNIQUE INDEX %(name)s ON %(table)s (%(columns)s)"
|
||||||
sql_delete_unique = "DROP INDEX %(name)s"
|
sql_delete_unique = "DROP INDEX %(name)s"
|
||||||
|
sql_foreign_key_constraint = None
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
# Some SQLite schema alterations need foreign key constraints to be
|
# Some SQLite schema alterations need foreign key constraints to be
|
||||||
|
|
|
@ -10,16 +10,22 @@ class BaseConstraint:
|
||||||
def constraint_sql(self, model, schema_editor):
|
def constraint_sql(self, model, schema_editor):
|
||||||
raise NotImplementedError('This method must be implemented by a subclass.')
|
raise NotImplementedError('This method must be implemented by a subclass.')
|
||||||
|
|
||||||
|
def full_constraint_sql(self, model, schema_editor):
|
||||||
|
return schema_editor.sql_constraint % {
|
||||||
|
'name': schema_editor.quote_name(self.name),
|
||||||
|
'constraint': self.constraint_sql(model, schema_editor),
|
||||||
|
}
|
||||||
|
|
||||||
def create_sql(self, model, schema_editor):
|
def create_sql(self, model, schema_editor):
|
||||||
sql = self.constraint_sql(model, schema_editor)
|
sql = self.full_constraint_sql(model, schema_editor)
|
||||||
return schema_editor.sql_create_check % {
|
return schema_editor.sql_create_constraint % {
|
||||||
'table': schema_editor.quote_name(model._meta.db_table),
|
'table': schema_editor.quote_name(model._meta.db_table),
|
||||||
'check': sql,
|
'constraint': sql,
|
||||||
}
|
}
|
||||||
|
|
||||||
def remove_sql(self, model, schema_editor):
|
def remove_sql(self, model, schema_editor):
|
||||||
quote_name = schema_editor.quote_name
|
quote_name = schema_editor.quote_name
|
||||||
return schema_editor.sql_delete_check % {
|
return schema_editor.sql_delete_constraint % {
|
||||||
'table': quote_name(model._meta.db_table),
|
'table': quote_name(model._meta.db_table),
|
||||||
'name': quote_name(self.name),
|
'name': quote_name(self.name),
|
||||||
}
|
}
|
||||||
|
@ -46,10 +52,7 @@ class CheckConstraint(BaseConstraint):
|
||||||
compiler = connection.ops.compiler('SQLCompiler')(query, connection, 'default')
|
compiler = connection.ops.compiler('SQLCompiler')(query, connection, 'default')
|
||||||
sql, params = where.as_sql(compiler, connection)
|
sql, params = where.as_sql(compiler, connection)
|
||||||
params = tuple(schema_editor.quote_value(p) for p in params)
|
params = tuple(schema_editor.quote_value(p) for p in params)
|
||||||
return schema_editor.sql_check % {
|
return schema_editor.sql_check_constraint % {'check': sql % params}
|
||||||
'name': schema_editor.quote_name(self.name),
|
|
||||||
'check': sql % params,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: check='%s' name=%r>" % (self.__class__.__name__, self.check, self.name)
|
return "<%s: check='%s' name=%r>" % (self.__class__.__name__, self.check, self.name)
|
||||||
|
|
|
@ -293,6 +293,13 @@ Database backend API
|
||||||
* Third party database backends must implement support for partial indexes or
|
* Third party database backends must implement support for partial indexes or
|
||||||
set ``DatabaseFeatures.supports_partial_indexes`` to ``False``.
|
set ``DatabaseFeatures.supports_partial_indexes`` to ``False``.
|
||||||
|
|
||||||
|
* Several ``SchemaEditor`` attributes are changed:
|
||||||
|
|
||||||
|
* ``sql_create_check`` is replaced with ``sql_create_constraint``.
|
||||||
|
* ``sql_delete_check`` is replaced with ``sql_delete_constraint``.
|
||||||
|
* ``sql_create_fk`` is replaced with ``sql_foreign_key_constraint``,
|
||||||
|
``sql_constraint``, and ``sql_create_constraint``.
|
||||||
|
|
||||||
Admin actions are no longer collected from base ``ModelAdmin`` classes
|
Admin actions are no longer collected from base ``ModelAdmin`` classes
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
from django.db import IntegrityError, models
|
from django.db import IntegrityError, connection, models
|
||||||
from django.db.models.constraints import BaseConstraint
|
from django.db.models.constraints import BaseConstraint
|
||||||
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import Product
|
from .models import Product
|
||||||
|
|
||||||
|
|
||||||
|
def get_constraints(table):
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
return connection.introspection.get_constraints(cursor, table)
|
||||||
|
|
||||||
|
|
||||||
class BaseConstraintTests(SimpleTestCase):
|
class BaseConstraintTests(SimpleTestCase):
|
||||||
def test_constraint_sql(self):
|
def test_constraint_sql(self):
|
||||||
c = BaseConstraint('name')
|
c = BaseConstraint('name')
|
||||||
|
@ -37,3 +42,11 @@ class CheckConstraintTests(TestCase):
|
||||||
Product.objects.create(name='Valid', price=10, discounted_price=5)
|
Product.objects.create(name='Valid', price=10, discounted_price=5)
|
||||||
with self.assertRaises(IntegrityError):
|
with self.assertRaises(IntegrityError):
|
||||||
Product.objects.create(name='Invalid', price=10, discounted_price=20)
|
Product.objects.create(name='Invalid', price=10, discounted_price=20)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_table_check_constraints')
|
||||||
|
def test_name(self):
|
||||||
|
constraints = get_constraints(Product._meta.db_table)
|
||||||
|
expected_name = 'price_gt_discounted_price'
|
||||||
|
if connection.features.uppercases_column_names:
|
||||||
|
expected_name = expected_name.upper()
|
||||||
|
self.assertIn(expected_name, constraints)
|
||||||
|
|
|
@ -2145,29 +2145,29 @@ class SchemaTests(TransactionTestCase):
|
||||||
self.assertNotIn(constraint_name, self.get_constraints(model._meta.db_table))
|
self.assertNotIn(constraint_name, self.get_constraints(model._meta.db_table))
|
||||||
|
|
||||||
constraint_name = "CamelCaseUniqConstraint"
|
constraint_name = "CamelCaseUniqConstraint"
|
||||||
editor.execute(
|
editor.execute(editor._create_unique_sql(model, [field.column], constraint_name))
|
||||||
editor.sql_create_unique % {
|
|
||||||
"table": editor.quote_name(table),
|
|
||||||
"name": editor.quote_name(constraint_name),
|
|
||||||
"columns": editor.quote_name(field.column),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if connection.features.uppercases_column_names:
|
if connection.features.uppercases_column_names:
|
||||||
constraint_name = constraint_name.upper()
|
constraint_name = constraint_name.upper()
|
||||||
self.assertIn(constraint_name, self.get_constraints(model._meta.db_table))
|
self.assertIn(constraint_name, self.get_constraints(model._meta.db_table))
|
||||||
editor.alter_field(model, get_field(unique=True), field, strict=True)
|
editor.alter_field(model, get_field(unique=True), field, strict=True)
|
||||||
self.assertNotIn(constraint_name, self.get_constraints(model._meta.db_table))
|
self.assertNotIn(constraint_name, self.get_constraints(model._meta.db_table))
|
||||||
|
|
||||||
if editor.sql_create_fk:
|
if editor.sql_foreign_key_constraint:
|
||||||
constraint_name = "CamelCaseFKConstraint"
|
constraint_name = "CamelCaseFKConstraint"
|
||||||
|
fk_sql = editor.sql_foreign_key_constraint % {
|
||||||
|
"column": editor.quote_name(column),
|
||||||
|
"to_table": editor.quote_name(table),
|
||||||
|
"to_column": editor.quote_name(model._meta.auto_field.column),
|
||||||
|
"deferrable": connection.ops.deferrable_sql(),
|
||||||
|
}
|
||||||
|
constraint_sql = editor.sql_constraint % {
|
||||||
|
"name": editor.quote_name(constraint_name),
|
||||||
|
"constraint": fk_sql,
|
||||||
|
}
|
||||||
editor.execute(
|
editor.execute(
|
||||||
editor.sql_create_fk % {
|
editor.sql_create_constraint % {
|
||||||
"table": editor.quote_name(table),
|
"table": editor.quote_name(table),
|
||||||
"name": editor.quote_name(constraint_name),
|
"constraint": constraint_sql,
|
||||||
"column": editor.quote_name(column),
|
|
||||||
"to_table": editor.quote_name(table),
|
|
||||||
"to_column": editor.quote_name(model._meta.auto_field.column),
|
|
||||||
"deferrable": connection.ops.deferrable_sql(),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if connection.features.uppercases_column_names:
|
if connection.features.uppercases_column_names:
|
||||||
|
|
Loading…
Reference in New Issue