2011-10-28 12 views
130

私はマルチテナントアプリケーションで作業しています。このアプリケーションでは、フォーム内に追加データを収集してデータを報告するための独自のデータフィールドを管理者が定義できます。後者のビットがJSONFieldない偉大なオプションになりますので、代わりに私は、次の解決策がありますCustomDataFieldはサイトへのForeignKeyを持っているかDjangoの動的モデルフィールド

class CustomDataField(models.Model): 
    """ 
    Abstract specification for arbitrary data fields. 
    Not used for holding data itself, but metadata about the fields. 
    """ 
    site = models.ForeignKey(Site, default=settings.SITE_ID) 
    name = models.CharField(max_length=64) 

    class Meta: 
     abstract = True 

class CustomDataValue(models.Model): 
    """ 
    Abstract specification for arbitrary data. 
    """ 
    value = models.CharField(max_length=1024) 

    class Meta: 
     abstract = True 

注 - 各サイトは、カスタムデータフィールドの異なるセットを持っていますが、同じを使用します。データベース。 その後、様々な具体的なデータフィールドは次のように定義できます。これは、次の使用につながる

class UserCustomDataField(CustomDataField): 
    pass 

class UserCustomDataValue(CustomDataValue): 
    custom_field = models.ForeignKey(UserCustomDataField) 
    user = models.ForeignKey(User, related_name='custom_data') 

    class Meta: 
     unique_together=(('user','custom_field'),) 

custom_field = UserCustomDataField.objects.create(name='zodiac', site=my_site) #probably created in the admin 
user = User.objects.create(username='foo') 
user_sign = UserCustomDataValue(custom_field=custom_field, user=user, data='Libra') 
user.custom_data.add(user_sign) #actually, what does this even do? 

しかし、これは特に、手動で関連するデータを作成するために必要で、非常に不格好な感じとそれを具体的なモデルに関連付けます。より良いアプローチがありますか?前emptively破棄された

オプション:

  • カスタムSQLオンザフライテーブルを変更します。部分的には、これは拡張性がなく、部分的にはハックが大きすぎるためです。
  • NoSQLのようなスキーマレスソリューション。私は彼らに対して何もしていないが、彼らはまだよく適合していない。最終的にこのデータと入力されており、第三者報告アプリケーションを使用する可能性があります。
  • 上記のJSONFieldは、クエリでうまく機能しないため、今日のよう
+6

プリemptively、これはこれらの質問のいずれかではありません。 http://stackoverflow.com/questions/7801729/django-model-with-dynamic-attributes http://stackoverflow.com/questions/ 2854656/per-instance-dynamic-fields-django-model – GDorn

答えて

242

、特定のストレージバックエンドを必要とする4つの利用可能なアプローチ、それらの2があります。

  1. Django-eavは、(元のパッケージはもはやmantainedではありませんが、いくつかを持っていますthriving forks

    この解決方法は、データモデルは、本質的に、オブジェクトの動的属性を格納するために複数のテーブルを使用します。

    • は、いくつかの純粋でシンプルなDjangoモデルを使用して動的フィールドを表現しているため、理解しやすく、データベースに依存しません。
    • あなたが効果的のような簡単なコマンドでDjangoのモデルに動的属性のストレージをアタッチ/デタッチすることができます:

      eav.unregister(Encounter) 
      eav.register(Patient) 
      
    • Nicely integrates with Django admin

    • 同時に非常に強力です。

    マイナス面:

    • ない、非常に効率的。これは、EAVパターン自体の批判であり、列フォーマットのデータをモデルのキーと値のペアのセットに手動でマージする必要があります。
    • データの完全性を維持するためには、複数列のユニークなキー制約が必要です。これは一部のデータベースでは非効率的な場合があります。
    • one of the forksを選択する必要があります。公式パッケージは維持されておらず、明確な指導者が存在しないためです。

    使用量が非常に簡単です:PostgreSQLので

    import eav 
    from app.models import Patient, Encounter 
    
    eav.register(Encounter) 
    eav.register(Patient) 
    Attribute.objects.create(name='age', datatype=Attribute.TYPE_INT) 
    Attribute.objects.create(name='height', datatype=Attribute.TYPE_FLOAT) 
    Attribute.objects.create(name='weight', datatype=Attribute.TYPE_FLOAT) 
    Attribute.objects.create(name='city', datatype=Attribute.TYPE_TEXT) 
    Attribute.objects.create(name='country', datatype=Attribute.TYPE_TEXT) 
    
    self.yes = EnumValue.objects.create(value='yes') 
    self.no = EnumValue.objects.create(value='no') 
    self.unkown = EnumValue.objects.create(value='unkown') 
    ynu = EnumGroup.objects.create(name='Yes/No/Unknown') 
    ynu.enums.add(self.yes) 
    ynu.enums.add(self.no) 
    ynu.enums.add(self.unkown) 
    
    Attribute.objects.create(name='fever', datatype=Attribute.TYPE_ENUM,\ 
                 enum_group=ynu) 
    
    # When you register a model within EAV, 
    # you can access all of EAV attributes: 
    
    Patient.objects.create(name='Bob', eav__age=12, 
              eav__fever=no, eav__city='New York', 
              eav__country='USA') 
    # You can filter queries based on their EAV fields: 
    
    query1 = Patient.objects.filter(Q(eav__city__contains='Y')) 
    query2 = Q(eav__city__contains='Y') | Q(eav__fever=no) 
    
  2. hstoreの、JSONまたはJSONBフィールド

    PostgreSQLは、いくつかのより複雑なデータ型をサポートしています。ほとんどはサードパーティのパッケージでサポートされていますが、近年Djangoはdjango.contrib.postgres.fieldsにそれらを採用しています。

    HStoreFieldは:

    Django-hstoreはもともと、サードパーティ製のパッケージだったが、Djangoの1.8は、いくつかの他のPostgreSQLでサポートされているフィールド型と一緒に、ビルトインとしてHStoreFieldを追加しました。

    このアプローチは、ダイナミックフィールドとリレーショナルデータベースの両方の世界を最大限に活用できるという点では優れています。ただし、hstoreはnot ideal performance-wiseです。特に、1つのフィールドに数千のアイテムを格納することになった場合は、値の文字列もサポートしています。

    あなたはこのようにそれを使用することができますDjangoのシェルで
    #app/models.py 
    from django.contrib.postgres.fields import HStoreField 
    class Something(models.Model): 
        name = models.CharField(max_length=32) 
        data = models.HStoreField(db_index=True) 
    

    >>> instance = Something.objects.create(
           name='something', 
           data={'a': '1', 'b': '2'} 
          ) 
    >>> instance.data['a'] 
    '1'   
    >>> empty = Something.objects.create(name='empty') 
    >>> empty.data 
    {} 
    >>> empty.data['a'] = '1' 
    >>> empty.save() 
    >>> Something.objects.get(name='something').data['a'] 
    '1' 
    

    あなたはhstoreのフィールドに対するインデックス付きクエリを発行できます。JSONField

    # equivalence 
    Something.objects.filter(data={'a': '1', 'b': '2'}) 
    
    # subset by key/value mapping 
    Something.objects.filter(data__a='1') 
    
    # subset by list of keys 
    Something.objects.filter(data__has_keys=['a', 'b']) 
    
    # subset by single key 
    Something.objects.filter(data__has_key='a')  
    

    を:

    JSON/JSONBフィールドは、キー/値だけでなく、JSONエンコード可能なデータ型をサポートしますeのペアだけでなく、(JSONBの場合)Hstoreよりもコンパクトで高速になる傾向があります。 いくつかのパッケージは、内蔵された保管用JSONBを使用してdjango-pgfields含むJSON/JSONBフィールドを実装しますが、Djangoの1.9、のようJSONFieldJSONFieldは、HStoreFieldに似ており、大規模な辞書ではパフォーマンスが向上する可能性があります。また、整数、ブール値、ネストされた辞書など、文字列以外の型もサポートしています。シェルで作成

    #app/models.py 
    from django.contrib.postgres.fields import JSONField 
    class Something(models.Model): 
        name = models.CharField(max_length=32) 
        data = JSONField(db_index=True) 
    

    >>> instance = Something.objects.create(
           name='something', 
           data={'a': 1, 'b': 2, 'nested': {'c':3}} 
          ) 
    

    インデックス付きのクエリが可能ですネスティングを除いて、HStoreFieldとほぼ同じです。複雑なインデックスは、手動での作成(またはスクリプト化された移行)が必要な場合があります。

    >>> Something.objects.filter(data__a=1) 
    >>> Something.objects.filter(data__nested__c=3) 
    >>> Something.objects.filter(data__has_key='a') 
    
  3. Django MongoDB

    や他のNoSQL Djangoの適応 - 彼らとあなたは完全に動的なモデルを持つことができます。

    のNoSQL Djangoのライブラリは素晴らしいですが、あなたは他のものの中ListFieldで多対多を交換する必要があります標準のDjangoからDjango-nonrelに移行し、例えば、彼らはDjangoの互換性は100%ではないことに注意してください。

    チェックアウトこのジャンゴのMongoDB例:

    from djangotoolbox.fields import DictField 
    
    class Image(models.Model): 
        exif = DictField() 
    ... 
    
    >>> image = Image.objects.create(exif=get_exif_data(...)) 
    >>> image.exif 
    {u'camera_model' : 'Spamcams 4242', 'exposure_time' : 0.3, ...} 
    

    あなたも、任意のDjangoのモデルのembedded listsを作成することができます。

    class Container(models.Model): 
        stuff = ListField(EmbeddedModelField()) 
    
    class FooModel(models.Model): 
        foo = models.IntegerField() 
    
    class BarModel(models.Model): 
        bar = models.CharField() 
    ... 
    
    >>> Container.objects.create(
        stuff=[FooModel(foo=42), BarModel(bar='spam')] 
    ) 
    
  4. Django-mutant: Dynamic models based on syncdb and South-hooks

    Django-mutantは完全に動的な外部キーとM2Mを実装フィールド。そしてWill Hardyとマイケル・ホールで信じられないほどのややハックソリューションに触発されています。

    これらのすべて

    にもかかわらず、堅牢かつ生産(relevant source code)でテストされているWill Hardy's talk at DjangoCon 2011(それを見て!)によると、Djangoの南フック、に基づいています。

    implement thisにまずMichael Hallました。

    はい、これは魔法です。これらのアプローチでは、完全に動的なDjangoのアプリケーション、モデル、およびフィールドと任意のリレーショナルデータベースバックエンドを実現できます。しかし、どんな費用で?アプリケーションの安定性は重い使用に苦しんでいますか?これらは考慮すべき問題です。同時のデータベース変更要求を許可するには、適切なlockを維持する必要があります。

    あなたはマイケル・ホールのLIBを使用している場合は、あなたのコードは次のようになります。

    from dynamo import models 
    
    test_app, created = models.DynamicApp.objects.get_or_create(
             name='dynamo' 
            ) 
    test, created = models.DynamicModel.objects.get_or_create(
            name='Test', 
            verbose_name='Test Model', 
            app=test_app 
           ) 
    foo, created = models.DynamicModelField.objects.get_or_create(
            name = 'foo', 
            verbose_name = 'Foo Field', 
            model = test, 
            field_type = 'dynamiccharfield', 
            null = True, 
            blank = True, 
            unique = False, 
            help_text = 'Test field for Foo', 
           ) 
    bar, created = models.DynamicModelField.objects.get_or_create(
            name = 'bar', 
            verbose_name = 'Bar Field', 
            model = test, 
            field_type = 'dynamicintegerfield', 
            null = True, 
            blank = True, 
            unique = False, 
            help_text = 'Test field for Bar', 
           ) 
    
+31

これはかなり徹底的です。 – Edwin

+0

@GDorn、更新をありがとう! –

+2

このトピックは最近DjangoCon 2013ヨーロッパで語られました:http://www.slideshare.net/schacki/django-dynamic-models20130502?from_search=2とhttp://www.youtube.com/watch?v=67wcGdk4aCc –

3

さらなる研究が、これはDjangoのために実装されたEntity Attribute Valueデザインパターンのやや特殊なケースであることが明らかになりましたいくつかのパッケージによって。

まず、オリジナルのeav-djangoプロジェクトがPyPiにあります。

第2に、最初のプロジェクトのより最近のフォークdjango-eavがあります。これは主に、サードパーティのアプリケーションでdjangoの独自のモデルやモデルでEAVを使用できるようにするリファクタリングです。

+0

私はwikiにそれを含めます。 –

+1

私はEAVがダイナミックモデリングの特殊なケースであるということを逆に言います。一意のIDが含まれている場合は、「セマンティックウェブ」コミュニティで「トリプル」または「クワッド」と呼ばれるコミュニティで頻繁に使用されます。ただし、SQLテーブルを動的に作成および変更できるメカニズムほど効率的ではありません。 – Cerin

+0

@GDomはeav-djangoを最初に選んだのですか?上記のどのオプションを選択したのですか? – Moreno

13

私はさらにジャンゴ・ダイナモアイデアをプッシュに取り組んできました。プロジェクトはまだ文書化されていませんが、https://github.com/charettes/django-mutantでコードを読むことができます。

実際にはFKやM2M分野(contrib.related参照)も動作し、それはあなた自身のカスタムフィールドのラッパーを定義することも可能です。

あなたがモデルプロキシ、抽象的またはミックスインをサブクラス化することができますので、このようなunique_togetherや発注プラスモデル拠点としてモデルオプションのサポートもあります。

私は実際に廃止された定義を使用してそれらを防止しながら、モデルの定義は、複数のDjangoの実行中のインスタンスaccross共有することができることを確認していないメモリ内のロック機構に取り組んでいます。

プロジェクトはまだ非常にアルファであるが、私は準備ができて、生産にそれを取る必要がありますので、それは私のプロジェクトの一つの礎技術です。大きな計画はdjango-nonrelもサポートしているので、mongodbドライバを利用することができます。

+1

こんにちは、サイモン! githubで作成した直後に[私のwikiの回答](http://stackoverflow.com/a/7934577/497056)にあなたのプロジェクトへのリンクを含めました。 :) :))あなたにstackoverflowでお会いできてうれしい! –

関連する問題