Database Django:“;“软”字;没有数据库完整性检查的ForeignField
我有一个Django项目,它有多个Django“应用程序”。其中一个模型表示来自外部源的数据(我不控制这些数据) 我希望我的其他应用程序能够引用此“外部应用程序”,但我希望避免所有数据库完整性检查的模糊性。我不希望db对这些“软外键”有任何限制 您知道如何编写一个自定义字段来模拟真实的Django ForeignKey,而不在数据库上创建硬约束吗 也许这已经存在,但我在谷歌上没有任何运气 提前感谢您的帮助:-) 注意:我知道这个系统有内容类型。但我不想要一般的关系。我只希望与已识别模型的特定关系没有硬完整性约束 编辑: 我找到了相关链接:Database Django:“;“软”字;没有数据库完整性检查的ForeignField,database,django-models,foreign-keys,constraints,Database,Django Models,Foreign Keys,Constraints,我有一个Django项目,它有多个Django“应用程序”。其中一个模型表示来自外部源的数据(我不控制这些数据) 我希望我的其他应用程序能够引用此“外部应用程序”,但我希望避免所有数据库完整性检查的模糊性。我不希望db对这些“软外键”有任何限制 您知道如何编写一个自定义字段来模拟真实的Django ForeignKey,而不在数据库上创建硬约束吗 也许这已经存在,但我在谷歌上没有任何运气 提前感谢您的帮助:-) 注意:我知道这个系统有内容类型。但我不想要一般的关系。我只希望与已识别模型的特定关系
注意:我使用South来管理我的数据库模式,所以我想我也需要对此做些什么。但这可能超出了本文的主题:)您可以尝试使用非托管模型:
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
不会为其创建db表。不过,它的表现将与其他型号完全相同。借鉴marianobianchi的评论,ForeignKey.on_delete的一个选项是
什么都不做:不要采取行动。如果您的数据库后端强制引用完整性,这将导致IntegrityError,除非您手动将SQL ON DELETE约束添加到数据库字段(可能使用初始SQL)
这与在db级别禁用外键约束相结合应该可以实现这一点。据我所知,有两种方法可以做到这一点。可以完全按照以下方式禁用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 db后端似乎也提供了一个约束\u检查\u禁用的上下文管理器,因此您可以将相关的db访问封装在如下代码中,以避免在整个过程中禁用检查:
from django.db import connection
with connection.constraint_checks_disabled():
do_stuff()
我尝试了一些类似Izz ad Din 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)
伙计们
我设法做到了我想要的
首先,我创建了一个新字段:
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
因为我使用South来管理我的数据库模式,所以我必须添加以下内容:
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], [r'^ecm\.lib\.softfk\.SoftForeignKey'])
然后,我不得不使用monkey patch south,这样它就考虑了no\u db\u约束
参数。创建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
以及:
这真的很难看,但我没有找到其他方法
现在,您可以像使用普通的ForeignKey一样使用SoftForeignKey字段,只是您没有任何引用完整性强制
完整的monkey补丁请参见此处:如果您只想禁用某个字段上的ForeignKey约束检查,那么只需向该字段添加
db_constraint=False
user = models.ForeignKey('User', db_constraint=False)
另见:
我通过使用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 ForeignKey的所有功能中受益,而不受db约束。例如,我希望能够从该
SoftForeignKey
引用的表中删除一行,而无需级联或将键设置为NULL
。如果一个对象引用了目标表中不存在的行,它应该引发一个ObjectDoesNotExist
异常。但我希望数据库接受这种状态。可能有帮助。您的问题是使用:两个外键的相关\u name='destinations'分别使用托管\u目的地和非托管\u目的地。这正是我想要的。这与一个巨大的分区表相结合,PG12(still)和一个自定义ForeignKeyClass在查询时覆盖默认instance.fieldname以包含分区约束所需的修复。覆盖models.ForeignKey上的get_extra_descriptor_过滤器。现在,我在Django+PG 12中获得了一个类似ForeignKey的对象,用于大规模分区表。这种方法是否适用于具有ForeignKey的应用程序/软件包,用于其他应用程序?@EliasPrado我非常确定答案是肯定的,但您能提供一个示例吗?您希望与哪些应用建立通用关系?
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')