172

一部のユーザーが (管理者を介して) 独自のデータ フィールドを定義して、フォーム内の追加データを収集し、データをレポートできるマルチテナントアプリケーションに取り組んでいます。後者のビットにより、JSONField は優れたオプションではないため、代わりに次の解決策があります。

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

CustomDataField に Site への ForeignKey があることに注意してください。各 Site には異なるカスタム データ フィールドのセットがありますが、同じデータベースを使用します。次に、さまざまな具体的なデータ フィールドを次のように定義できます。

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?

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

先制的に破棄されたオプション:

  • オンザフライでテーブルを変更するためのカスタム SQL。部分的にはこれが拡張できないため、部分的にはハックが多すぎるためです。
  • NoSQL のようなスキーマレス ソリューション。私は彼らに反対することは何もありませんが、それでも彼らはうまく適合していません. 最終的にこのデータ入力され、サードパーティのレポート アプリケーションを使用する可能性があります。
  • 上記の JSONField は、クエリではうまく機能しないためです。
4

3 に答える 3

288

現在、利用可能なアプローチは 4 つあります。そのうちの 2 つは、特定のストレージ バックエンドを必要とします。

  1. Django-eav (元のパッケージは管理されなくなりましたが、いくつかの繁栄しているフォークがあります)

    このソリューションは、 Entity Attribute Valueデータ モデルに基づいており、基本的に、複数のテーブルを使用してオブジェクトの動的属性を格納します。このソリューションの優れた点は、次のとおりです。

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

      eav.unregister(Encounter)
      eav.register(Patient)
      
    • Django admin とうまく統合します。

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

    欠点:

    • あまり効率的ではありません。これは、列形式からモデル内のキーと値のペアのセットにデータを手動でマージする必要がある EAV パターン自体に対する批判です。
    • 維持するのが難しくなります。データの整合性を維持するには、複数列の一意のキー制約が必要ですが、一部のデータベースでは効率が悪い場合があります。
    • 公式パッケージはメンテナンスされなくなり、明確なリーダーが存在しないため、 forks のいずれかを選択する必要があります。

    使用方法は非常に簡単です。

    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. PostgreSQL の Hstore、JSON、または JSONB フィールド

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

    HStoreField :

    Django-hstoreはもともとサードパーティのパッケージでしたが、Django 1.8 では組み込みとしてHStoreFieldが追加され、PostgreSQL がサポートする他のいくつかのフィールド タイプも追加されました。

    このアプローチは、動的フィールドとリレーショナル データベースの両方の長所を活用できるという意味で優れています。ただし、hstore は、特に 1 つのフィールドに何千ものアイテムを格納することになる場合は、パフォーマンスの点で理想的ではありません。また、値として文字列のみをサポートします。

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

    Django のシェルでは、次のように使用できます。

    >>> 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 フィールドに対してインデックス付きクエリを発行できます。

    # 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フィールド:

    JSON/JSONB フィールドは、キーと値のペアだけでなく、JSON でエンコード可能な任意のデータ型をサポートしますが、Hstore よりも高速で (JSONB の場合) コンパクトになる傾向があります。いくつかのパッケージはdjango-pgfieldsを含む JSON/JSONB フィールドを実装していますが、Django 1.9 の時点で、JSONFieldはストレージに JSONB を使用する組み込みです。 JSONFieldは 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. ジャンゴ MongoDB

    または、他の NoSQL Django の適応 - それらを使用すると、完全に動的なモデルを作成できます。

    NoSQL Django ライブラリは優れていますが、100% Django と互換性があるわけではないことに注意してください。たとえば、標準の Django からDjango-nonrelに移行するには、ManyToMany をListFieldに置き換える必要があります。

    この Django 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 モデルの埋め込みリストを作成することもできます:

    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: syncdb と South-hooks に基づく動的モデル

    Django-mutantは、完全に動的な外部キーと m2m フィールドを実装しています。そして、 Will Hardyと Michael Hallによる信じられないほどハックなソリューションに触発されています。

    これらはすべて Django South フックに基づいていますが、DjangoCon 2011 での Will Hardy の講演 (見てください!)によると、それでも堅牢であり、本番環境でテストされています (関連するソース コード)。

    これを最初に実装したのはMichael Hallでした。

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

    Michael Halls 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',
                   )
    
于 2011-10-28T20:32:06.570 に答える
13

私は、django-dynamo のアイデアをさらに推し進めることに取り組んできました。このプロジェクトはまだ文書化されていませんが、 https://github.com/charettes/django-mutantでコードを読むことができます。

実際には、FK および M2M フィールド (contrib.related を参照) も機能し、独自のカスタム フィールドのラッパーを定義することもできます。

また、unique_together や順序付けなどのモデル オプションとモデル ベースもサポートされているため、モデル プロキシ、アブストラクト、またはミックスインをサブクラス化できます。

私は実際に、メモリ内ではないロックメカニズムに取り組んでおり、古い定義を使用しないようにしながら、複数の django 実行インスタンス間でモデル定義を共有できるようにしています。

このプロジェクトはまだ非常にアルファ版ですが、私のプロジェクトの 1 つの基礎となるテクノロジであるため、本番環境に移行する必要があります。大きな計画では、django-nonrel もサポートしているため、mongodb ドライバーを活用できます。

于 2012-01-29T17:44:34.883 に答える
4

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

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

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

于 2011-11-17T21:01:41.393 に答える