Django MSSQL:覆盖外键约束名称生成

And*_*ian 5 python sql-server django python-3.9 django-mssql-backend

我正在尝试使用 django-mssql-backend 在 Django 3.0 中创建一个数据库模型,作为 SQL Server 2019 的数据库后端。该数据库对其中包含的表使用多个架构,其中一些表是非托管的(已经存在) ,以及通过迁移从头开始创建的其他内容。为了使多个模式正常工作,我使用了在此处的另一个答案中找到的技巧,建议按如下方式格式化表名:

class MyModel(models.Model):
    ...

    class Meta:
        db_table = 'schema].[table'
Run Code Online (Sandbox Code Playgroud)

这样 SQL 就可以编译为在外部自动形成的方括号中完成架构/表定义。这样做的问题是,ForeignKey 对象使用此表名称生成其约束名称,这会导致出现无效的约束名称,从而导致在创建表完成后迁移失败,并且需要创建约束。\

它们生成如下:
[schema1].[table1_colname_id_bc165567_fk2_schema2].[table_colname]

有没有办法覆盖这种行为?如果可以通过分叉后端并添加手动编译代码来覆盖这一点,我将如何去做呢?否则,如何在使用多个模式并充分利用 Django 附带的 ORM/迁移的同时在模型中拥有外键?

And*_*ian 3

我最终弄清楚了。分叉 django-mssql-backend 后,我覆盖了模块内类中的_create_index_name_fk_constraint_name方法,将表名称更改为,这省略了字符串的模式 hack 部分和附加的开括号,但不应该干扰不这样做的表也应用了该 hack。DatabaseSchemaEditorsql_server.pyodbc.schemasplit('[')[-1]

这是重写的方法
(大部分代码与 django.db.backends.base.schema 中的原始实现相同):

def _create_index_name(self, table_name, column_names, suffix=""):
        """
        Generate a unique name for an index/unique constraint.
        The name is divided into 3 parts: the table name, the column names,
        and a unique digest and suffix.
        """

        # CHANGE HERE (table_name to table_name.split('[')[-1]
        _, table_name = split_identifier(table_name.split('[')[-1])
        hash_suffix_part = '%s%s' % (names_digest(table_name, *column_names, length=8), suffix)
        max_length = self.connection.ops.max_name_length() or 200
        # If everything fits into max_length, use that name.
        index_name = '%s_%s_%s' % (table_name, '_'.join(column_names), hash_suffix_part)
        if len(index_name) <= max_length:
            return index_name
        # Shorten a long suffix.
        if len(hash_suffix_part) > max_length / 3:
            hash_suffix_part = hash_suffix_part[:max_length // 3]
        other_length = (max_length - len(hash_suffix_part)) // 2 - 1
        index_name = '%s_%s_%s' % (
            table_name[:other_length],
            '_'.join(column_names)[:other_length],
            hash_suffix_part,
        )
        # Prepend D if needed to prevent the name from starting with an
        # underscore or a number (not permitted on Oracle).
        if index_name[0] == "_" or index_name[0].isdigit():
            index_name = "D%s" % index_name[:-1]
        return index_name

    def _fk_constraint_name(self, model, field, suffix):
        def create_fk_name(*args, **kwargs):
            return self.quote_name(self._create_index_name(*args, **kwargs))

        return ForeignKeyName(
            model._meta.db_table.split('[')[-1],
            [field.column],
            # CHANGE HERE (db_table to db_table.split('[')[-1]
            split_identifier(field.target_field.model._meta.db_table.split('[')[-1])[1],
            [field.target_field.column],
            suffix,
            create_fk_name,
        )
Run Code Online (Sandbox Code Playgroud)

如果 Django 的未来版本继续不支持多个模式,希望这对将来遇到类似问题的其他人有用。

编辑:我已经在 django-mssql-backend 存储库上创建了一个拉取请求来合并这些更改,因此如果它获得批准,这个问题的上下文可能不会成为将来人们的问题。