Django:没有数据库完整性检查的"Soft"ForeignField

Rob*_*bin 12 database constraints foreign-keys django-models

我有一个Django项目,有多个django"应用程序".其中一个模型表示来自外部源的数据(我不控制这些数据).

我希望我的其他应用程序能够引用此"外部应用程序",但我想避免数据库完整性检查的所有模糊.我不希望db对这些"软外键"有任何限制.

你知道如何编写一个自定义字段来模拟真正的Django ForeignKey而不在数据库上创建硬约束吗?

也许这已经存在,但我在谷歌上没有运气.

在此先感谢您的帮助 :-)

注意:我知道带有content_types 的通用关系系统.但我不想要通用关系.我希望只有没有硬完整性约束的特定关系才能识别模型.

编辑:

我找到了相关的链接:

但我没有找到适合我的问题的答案.:(

编辑2012年6月4日:

我深入研究了django的代码以找到需要做的事情,但我认为简单地继承ForeignKey是不够的.你能告诉我一些如何做的指示吗?

注意:我使用South来管理我的数据库模式,所以我想我也需要对此做些什么.但它可能不在这里:)

Jor*_*ris 7

如果您只想禁用对某个字段的 ForeignKey 约束检查,则只需添加db_constraint=False到该字段即可。

user = models.ForeignKey('User', db_constraint=False)
Run Code Online (Sandbox Code Playgroud)

另请参阅: Django - 如何防止创建数据库外键约束


dok*_*ebi 6

借助 marianobianchi 的评论,ForeignKey.on_delete 的选项之一是

DO_NOTHING:不采取任何行动。如果您的数据库后端强制执行引用完整性,则这将导致 IntegrityError,除非您手动向数据库字段添加 SQL ON DELETE 约束(可能使用初始 sql)。

这与在数据库级别禁用外键约束相结合应该可以解决问题。据我所知,有两种方法可以做到这一点。您可以像这样完全禁用 fk 约束:

from django.db.backend.signals import connection_created
from django.dispatch import receiver

@receiver(connection_created)
def disable_constraints(sender, connection):
    connection.disable_constraint_checking()
Run Code Online (Sandbox Code Playgroud)

看起来 django 数据库后端也提供了一个constraint_checks_disabled上下文管理器,因此您可以将相关的数据库访问包装在这样的代码中,以避免在整个过程中禁用检查:

from django.db import connection
with connection.constraint_checks_disabled():
    do_stuff()
Run Code Online (Sandbox Code Playgroud)


Rob*_*bin 3

各位,

我成功地做到了我想要的。

首先,我创建了一个新字段:

from django.db.models.deletion import DO_NOTHING
from django.db.models.fields.related import ForeignKey, ManyToOneRel

class SoftForeignKey(ForeignKey):
    """
    This field behaves like a normal django ForeignKey only without hard database constraints.
    """
    def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
        ForeignKey.__init__(self, to, to_field=to_field, rel_class=rel_class, **kwargs)
        self.on_delete = DO_NOTHING

    no_db_constraints = True
Run Code Online (Sandbox Code Playgroud)

由于我使用 South 来管理我的数据库架构,因此我必须添加以下内容:

from south.modelsinspector import add_introspection_rules
add_introspection_rules([], [r'^ecm\.lib\.softfk\.SoftForeignKey'])
Run Code Online (Sandbox Code Playgroud)

然后,我必须向南进行猴子修补,以便将no_db_constraints参数考虑在内。FK 约束的创建涉及两个函数:

from django.db.models.deletion import DO_NOTHING
from django.db.models.fields.related import ForeignKey, ManyToOneRel
from django.core.management.color import no_style
from south.db.generic import DatabaseOperations, invalidate_table_constraints, flatten

def column_sql(self, table_name, field_name, field, tablespace='', with_name=True, field_prepared=False):
    """
    Creates the SQL snippet for a column. Used by add_column and add_table.
    """

    # If the field hasn't already been told its attribute name, do so.
...
...
...

        if field.rel and self.supports_foreign_keys:
            # HACK: "soft" FK handling begin
            if not hasattr(field, 'no_db_constraints') or not field.no_db_constraints:
                self.add_deferred_sql(
                    self.foreign_key_sql(
                        table_name,
                        field.column,
                        field.rel.to._meta.db_table,
                        field.rel.to._meta.get_field(field.rel.field_name).column
                    )
                )
            # HACK: "soft" FK handling end

    # Things like the contrib.gis module fields have this in 1.1 and below
    if hasattr(field, 'post_create_sql'):
        for stmt in field.post_create_sql(no_style(), ta
....
....

# monkey patch South here
DatabaseOperations.column_sql = column_sql
Run Code Online (Sandbox Code Playgroud)

和:

from django.db.models.deletion import DO_NOTHING
from django.db.models.fields.related import ForeignKey, ManyToOneRel
from django.core.management.color import no_style
from south.db.generic import DatabaseOperations, invalidate_table_constraints, flatten

@invalidate_table_constraints
def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False):
    """
    Alters the given column name so it will match the given field.
    Note that conversion between the two by the database must be possible.
    Will not automatically add _id by default; to have this behavour, pass
    explicit_name=False.

    @param table_name: The name of the table to add the column to
    @param name: The name of the column to alter
    @param field: The new field definition to use
    """

    if self.dry_run:
        if self.debug:
...
...
    if not ignore_constraints:
        # Add back FK constraints if needed
        if field.rel and self.supports_foreign_keys:
            # HACK: "soft" FK handling begin
            if not hasattr(field, 'no_db_constraints') or not field.no_db_constraints:
                self.execute(
                    self.foreign_key_sql(
                        table_name,
                        field.column,
                        field.rel.to._meta.db_table,
                        field.rel.to._meta.get_field(field.rel.field_name).column
                    )
                )
            # HACK: "soft" FK handling end

# monkey patch South here
DatabaseOperations.alter_column = alter_column
Run Code Online (Sandbox Code Playgroud)

这确实很难看,但我没有找到其他方法。

现在,您可以像普通外键一样使用 SoftForeignKey 字段,只是您不会执行任何引用完整性。

请参阅此处了解完整的猴子补丁:http://eve-corp-management.org/projects/ecm/repository/entry/ecm/lib/softfk.py