Django的:“软” ForeignField没有数据库的完整性检查(Django: “Soft”

2019-07-29 10:26发布

我有一个具有多个Django的“应用程序”一个Django项目。 其中一人有模型来表示数据从外部源(我不控制这个数据)的到来。

我希望我的其他应用程序,以便能够有提及这个“外部应用程序”,但我想避免数据库完整性检查的所有的绒毛。 我不想分贝会对这些“软外键”的任何约束。

你知道我可以编写一个自定义字段,将模拟一个真实的Django ForeignKey的,而不创建数据库的硬约束?

也许这已经存在,但我并没有对谷歌的任何运气。

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

注:我知道的一般关系系统的CONTENT_TYPES。 但我不希望通用的关系。 我想只有不努力的完整性约束的特定关系,确定模型。

编辑:

我发现相关链接:

  • Django的ForeignKey的不需要参照完整性?
  • 谅解/ MySQL的又名欺骗在Django ForeignKey的关系

但我没有找到一个合适的回答我的问题。 :(

编辑2012年,6月4日:

我看着深入Django的代码,找出需要做什么,但我认为这只是继承ForeignKey的是不够的。 你能给我如何做到这一点的一些方向?

注:我使用的南来管理我的数据库架构,所以我想我需要做的东西太多。 但它可能是在这里的主题:)

Answer 1:

呦家伙,

我设法让我想要的东西。

首先,我创建了一个新的领域:

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

由于我使用南来管理我的数据库架构,我不得不补充一点:

from south.modelsinspector import add_introspection_rules
add_introspection_rules([], [r'^ecm\.lib\.softfk\.SoftForeignKey'])

于是,我只好猴子补丁南所以,它需要的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

和:

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

这实在是太丑了,但我没有找到另一种方式。

现在,您可以使用SoftForeignKey场完全一样,只是你不会有任何的借鉴完整性强制执行正常ForeignKey的。

在这里看到完整的猴子补丁: http://eve-corp-management.org/projects/ecm/repository/entry/ecm/lib/softfk.py



Answer 2:

如果你只是想禁用某个字段ForeignKey的约束检查,然后只需添加db_constraint=False到该领域。

user = models.ForeignKey('User', db_constraint=False)

参见: Django的-如何防止数据库外键约束创建



Answer 3:

我想类似伊茨广告锭Ruhulessin的建议的东西,但它没有工作,因为我有比“假FK”栏目等栏目。 我试过的代码是:

class DynamicPkg(models.Model):
    @property
    def cities(self):
        return City.objects.filter(dpdestinations__dynamic_pkg=self)


class DynamicPkgDestination(models.Model):
    dynamic_pkg = models.ForeignKey(DynamicPkg, related_name='destinations')
    # Indexed because we will be joining City.code to
    # DynamicPkgDestination.city_code and we want this to be fast.
    city_code = models.CharField(max_length=10, db_index=True)


class UnmanagedDynamicPkgDestination(models.Model):
    dynamic_pkg = models.ForeignKey(DynamicPkg, related_name='destinations')
    city = models.ForeignKey('City', db_column='city_code', to_field='code', related_name='dpdestinations')

    class Meta:
        managed = False
        db_table = DynamicPkgDestination._meta.db_table


class City(models.Model):
    code = models.CharField(max_length=10, unique=True)

和错误居然是:

Error: One or more models did not validate:
travelbox.dynamicpkgdestination: Accessor for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.
travelbox.dynamicpkgdestination: Reverse query name for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.
travelbox.unmanageddynamicpkgdestination: Accessor for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.
travelbox.unmanageddynamicpkgdestination: Reverse query name for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.

但是我没有想出通过使用代理模型的工作方案。 我还是要砍周围,防止从场中所包含的代理模型的一些Django的验证:

class DynamicPkg(models.Model):
    @property
    def cities(self):
        return City.objects.filter(dpdestinations__dynamic_pkg=self)



def proxify_model(new_class, base):
    """
    Like putting proxy = True in a model's Meta except it doesn't spoil your
    fun by raising an error if new_class contains model fields.
    """
    new_class._meta.proxy = True
    # Next 2 lines are what django.db.models.base.ModelBase.__new__ does when
    # proxy = True (after it has done its spoil-sport validation ;-)
    new_class._meta.setup_proxy(base)
    new_class._meta.concrete_model = base._meta.concrete_model


class DynamicPkgDestination(models.Model):
    dynamic_pkg = models.ForeignKey(DynamicPkg, related_name='destinations')
    # Indexed because we will be joining City.code to
    # DynamicPkgDestination.city_code and we want this to be fast.
    city_code = city_code_field(db_index=True)


class ProxyDynamicPkgDestination(DynamicPkgDestination):
    city = models.ForeignKey('City', db_column='city_code', to_field='code', related_name='dpdestinations')


proxify_model(ProxyDynamicPkgDestination, DynamicPkgDestination)


class City(models.Model):
    code = models.CharField(max_length=10, unique=True)


Answer 4:

你可以尝试使用非托管模式:

from django.db import models


class ReferencedModel(models.Model):
    pass


class ManagedModel(models.Model):
    my_fake_fk = models.IntegerField(
        db_column='referenced_model_id'
    )


class UnmanagedModel(models.Model):
    my_fake_fk = models.ForeignKey(
        ReferencedModel, 
        db_column='referenced_model_id'
    )

    class Meta:
        managed = False
        db_table = ManagedModel._meta.db_table

指定managed=False在模型元类将不会为它创建一个数据库表。 但是,它的行为完全像其他车型。



Answer 5:

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()

它看起来像Django的数据库后端提供constraint_checks_disabled上下文管理也一样,所以你可以包装相关的数据库中的代码访问这样避免整个禁用检查:

from django.db import connection
with connection.constraint_checks_disabled():
    do_stuff()


Answer 6:

我解决了这个用GenericForeignKey:

thing_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True)
thing_object_id = models.UUIDField(default=uuid.uuid4, blank=True, null=True)

thing = GenericForeignKey(ct_field='thing_content_type', fk_field='thing_object_id')

从积极的一面,这是超出现成的Django

在消极的一面,你有你的模型三个附加属性。

此外,反向关系不会自动工作,但对我来说,我可以接受这一点。



文章来源: Django: “Soft” ForeignField without database integrity checks