且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Django:“Soft” ForeignField无数据库完整性检查

更新时间:2023-02-02 21:45:35

$ p

我设法使我想要的。



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

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

class SoftForeignKey(ForeignKey):

该字段的行为像普通的django ForeignKey只有没有硬数据库约束

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'])

然后,我不得不向南猴子补丁,所以它需要 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):

创建列的SQL代码段,由add_column和add_table


#如果该字段尚未被告知其属性名称,请执行此操作。
...
...
...

如果field.rel和self.supports_foreign_keys:
#HACK:softFK处理开始
如果不是hasattr(字段'no_db_constraints')或不是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:softFK处理结束

#如果contrib.gis模块字段在1.1和更低版本
之间,如果hasattr(field,'post_create_sql'):
for stmt in field.post_create_sql(no_style(),ta
....
....

#monkey patch南这里
DatabaseOperations.column_sql = column_sql

And:

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

更改给定的列名称,以使其与给定字段匹配。
请注意,数据库之间的转换必须是可能的。
默认情况下不会自动添加_id;要有这个行为,通过
explicit_name = False。

@param table_name:将列添加到
@param name的表的名称:要更改的列的名称
@param field:新的字段定义使用


如果self.dry_run:
如果self.debug:
...
...
如果不是ignore_constraints:
#如果需要,添加FK约束
如果field.rel和self.supports_foreign_keys:
#HACK:softFK处理开始
如果不是hasattr(field,' no_db_constraints')或不是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:softFK处理结束

#猴子补丁南这里
DatabaseOperations.alter_column = alter_column

这真的很丑,但我没有找到其他方式。



现在,您可以像普通的ForeignKey一样使用SoftForeignKey字段,除非您没有任何参考完整性执行。



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


I have a Django project that has multiple django "apps". One of them has models to represent data coming from an external source (I do not control this data).

I want my other apps to be able to have references to this "external app" but I want to avoid all the fuzz of the database integrity checks. I don't want the db to have any constraints on these "soft foreign keys".

Do you know how I can code a custom field that will emulate a real Django ForeignKey without creating a hard constraint on the database?

Maybe this already exists, but I didn't have any luck on Google.

Thanks in advance for the help :-)

NB: I'm aware of the generic relations system with the content_types. But I don't want generic relations. I want specific relations to identified models only without hard integrity constraints.

EDIT:

I found related links:

But I didn't find a proper answer to my question. :(

EDIT 2012, june 4:

I've looked deep into django's code to find what needs to be done, but I think that simply subclassing ForeignKey will not be enough. Could you give me some directions on how to do this?

NB: I use South for managing my database schema, so I figure I'll need to do something about that too. But it may be out of the subject here :)

Yo guys,

I managed to make what I wanted.

First, I created a new field:

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

Since I use South to manage my database schema, I had to add this:

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

Then, I had to monkey patch south so that it takes the no_db_constraints parameter into account. There were two functions involved in the creation of FK constraints:

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

And:

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

This is really ugly but I didn't find another way.

Now you can use the SoftForeignKey field exactly like a normal ForeignKey except that you won't have any referencial integrity enforcement.

See here for the complete monkey-patch : http://eve-corp-management.org/projects/ecm/repository/entry/ecm/lib/softfk.py