2012-05-16 12 views
10

私は複数のdjango "apps"を持つDjangoプロジェクトを持っています。そのうちの1つに、外部ソースからのデータを表すモデルがあります(私はこのデータを制御しません)。Django:データベースの整合性チェックなしの "Soft" ForeignField

他のアプリがこの「外部アプリ」への参照を持つことができるようにしたいが、データベースの完全性チェックのすべての不安を避けたい。私は、DBにこれらの「ソフトな外部キー」に対する制約を持たせたくありません。

データベースにハード制約を作成せずに実際のDjango ForeignKeyをエミュレートするカスタムフィールドをどのようにコーディングできますか?

これは既に存在しているかもしれませんが、私はGoogleに何の不運も感じませんでした。

NB :-)助けを事前に

ありがとう:私はCONTENT_TYPESとgeneric relationsシステムの意識です。しかし、私は一般的な関係は望んでいません。ハードな整合性の制約がない場合にのみ、特定されたモデルとの特定の関係を欲しい。

EDIT:

私が見つけたの関連リンクは:

しかし、私は私の質問に適切な答えを見つけることができませんでした。 :(

EDIT 2012年、6月4日:

私は何をすべきかを見つけるために、Djangoのコードに深く見てきたが、私は単にのForeignKeyをサブクラス化することは十分ではないだろうと思い、あなたは私にいくつかの方向性を与えることができます。 ?

NBをこれを行う方法について:私は私のデータベーススキーマを管理するために、南使うので、私は、私はあまりにもそれについて何かをする必要があります理解しかし、それは、ここで対象としていない可能性があり:)

+1

それは本当に異種のキーではないのですか? –

+0

まあ、db制約なしでdjango ForeignKeyのすべての機能を利用したいと思っています。 – Robin

+0

たとえば、この 'SoftForeignKey'によって参照されるテーブルから行を削除するには、カスケードしたり、キーをNULLに設定する必要がありません。また、オブジェクトがターゲットテーブル内に存在しない行への参照を持つ場合、 'ObjectDoesNotExist'例外が発生します。しかし、私はデータベースがこの種の状態を受け入れるようにしたい。 – Robin

答えて

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 

私は私のデータベーススキーマを管理するために、南使用しているので、私はこれを追加する必要がありました:私は猿に持っていた、そして、

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

をパッチ南は、no_db_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 

そして:

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 

これは本当に醜いですが、私は別の方法を見つけることができませんでしたFK制約の作成に関与する2つの機能がありました。

ここで、SoftForeignKeyフィールドは、参照整合性の適用がないことを除いて、通常のForeignKeyとまったく同じように使用できます。

は、完全な猿パッチについてはこちらをご覧ください:

1

。アンマネージドモデルを試してみてください:

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 

Model Metaクラスにmanaged=Falseを指定しても、dbテーブルは作成されません。ただし、他のモデルとまったく同じように動作します。

1

marianobianchiさんのコメントのオフピギーバック、ForeignKey.on_deleteのための選択肢の一つは、

DO_NOTHINGです:処置を取る必要はありません。データベースバックエンドで参照整合性が強制されている場合、データベースフィールドにSQL ON DELETE制約を手動で追加しない限り、IntegrityErrorが発生します(おそらくはinitial SQLを使用します)。

これは、dbレベルでの外部キー制約を無効にすることと組み合わせて使用​​する必要があります。私が知ることから、これを行うには2つの方法があります。あなたは、このように完全にFK制約を無効にすることができます:あなたは、関連するDBは全体のチェックを無効にすることを避けるために、このようなコードでアクセス包むことができるようにそれは、DjangoのDBのバックエンドのように見えます

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

は、あまりにも、constraint_checks_disabledコンテキストマネージャを提供します。

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

私はIzzの広告喧騒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) 
+0

あなたの問題は使用しています:related_name = '両方のForeignKeysのdestinations'はそれぞれmanaged_destinationsとunmanaged_destinationsをそれぞれ使用します。 –

0

は私が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の

オンです負の側には、モデルに3つの追加の属性があります。

さらに、リバースリレーションは自動的には機能しませんが、私の場合は問題ありません。

関連する問題