Django動的モデルフィールド


161

一部のユーザーが独自のデータフィールドを(管理者経由で)定義して、フォームで追加のデータを収集し、そのデータについてレポートできるマルチテナント型アプリケーションに取り組んでいます。後者のビットは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がどのようにサイトへのForeignKeyを持っているかに注意してください-各サイトには異なるカスタムデータフィールドのセットがありますが、同じデータベースを使用します。次に、さまざまな具象データフィールドを次のように定義できます。

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は、クエリではうまく機能しないためです。

6
前emptively、これは、任意のこれらの質問ではありません: stackoverflow.com/questions/7801729/...の stackoverflow.com/questions/2854656/...
GDorn

回答:


277

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

  1. Django-eav(元のパッケージはもはや管理されていませんが、いくつかの盛んなフォークがあります

    このソリューションは、エンティティ属性値データモデルに基づいています。基本的に、オブジェクトの動的属性を格納するためにいくつかのテーブルを使用します。このソリューションの優れた点は次のとおりです。

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

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

    • 同時に、本当に強力です。

    欠点:

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

    使い方はかなり簡単です:

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

    JSONField

    JSON / JSONBフィールドは、キーと値のペアだけでなく、JSONでエンコード可能なデータ型をサポートするだけでなく、Hstoreよりも高速で(JSONBの場合)コンパクトになる傾向があります。django-pgfieldsを含むいくつかのパッケージはJSON / JSONBフィールドを実装していますが、Django 1.9 以降では、JSONFieldはJSONBをストレージに使用する組み込み型です。 JSONFieldHStoreFieldに似ており、大きな辞書を使用するとパフォーマンスが向上する可能性があります。また、整数、ブール値、ネストされた辞書など、文字列以外のタイプもサポートしています。

    #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ライブラリは優れていますが、Djangoと100%互換ではないことに注意してください。たとえば、標準の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フックに基づいています。DjangoCon2011 Wit it!)でのWill Hardyの講演によると、それでも堅牢であり、運用環境でテストされています関連するソースコード)。

    これを最初に実装したのは、マイケルホールでした。

    はい、これは魔法です。これらのアプローチにより、リレーショナルデータベースバックエンドで完全に動的な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',
                   )
    

3
このトピックはDjangoCon 2013 Europeで最近話題になりました:slideshare.net/schacki/…およびyoutube.com/watch?v=67wcGdk4aCc
Aleck Landgraf

Postgres> = 9.2でdjango-pgjsonを使用すると、postgresqlのjsonフィールドを直接使用できることにも注意してください。Django> = 1.7では、クエリのフィルターAPIは比較的正常です。Postgres> = 9.4では、クエリを高速化するために、jsonbフィールドのインデックスが改善されています。
GDorn 2015年

1
DjangoがHStoreFieldおよびJSONFieldをcontribに採用したことに言及するために本日更新されました。それは素晴らしいではないいくつかのフォームウィジェットが含まれていますが、管理者でデータを微調整する必要がある場合は機能します。
GDorn

13

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

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

unique_togetherやordering plus Modelベースなどのモデルオプションもサポートされているため、モデルプロキシ、抽象、ミックスインをサブクラス化できます。

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

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


1
こんにちは、サイモン!githubでプロジェクトを作成した直後にプロジェクトへのリンクをウィキの回答に含めました。:)))stackoverflowでお会いできて嬉しいです!
Ivan Kharlamov、2012年

4

さらなる調査により、これはエンティティ属性値のデザインパターンのやや特殊なケースであることが判明しました。これは、いくつかのパッケージによってDjangoに実装されています。

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

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


私はそれをウィキに含めます。
Ivan Kharlamov、2011

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

@GDomはeav-djangoを最初に選択しますか?上記のどのオプションを選択したのですか?
モレノ

1
@Moreno正しい選択は、特定のユースケースに大きく依存します。EAVとJsonFieldsの両方をさまざまな理由で使用しました。後者は現在Djangoによって直接サポートされているため、新しいプロジェクトでは、EAVテーブルでクエリを実行する必要がある特定の必要がない限り、最初にそれを使用します。JsonFieldsに対してもクエリを実行できることに注意してください。
GDorn 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.