[解決済み] Djangoの動的モデルフィールド
質問
私は今 マルチテナント アプリケーションで、一部のユーザーが独自のデータフィールドを定義して(管理者経由で)フォームに追加データを収集し、データについてレポートを作成することができます。 後者の場合、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つのアプローチがあり、そのうち2つは特定のストレージバックエンドを必要とします。
-
Django-eav (オリジナルのパッケージはもう管理されていませんが、いくつかの フォーク繁盛 )
このソリューションは エンティティの属性値 データモデルで、基本的には、オブジェクトの動的属性を格納するためにいくつかのテーブルを使用します。このソリューションの優れた点は、以下の通りです。
- は、いくつかの純粋でシンプルな Django モデルを使って動的なフィールドを表現しています。
-
のような簡単なコマンドで、Djangoモデルに動的属性ストレージを効果的にアタッチ/デタッチすることができます。
eav.unregister(Encounter) eav.register(Patient)
-
同時に、本当にパワフルであること。
デメリット
- あまり効率的でない。 これはむしろ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)
-
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では)コンパクトになる傾向があります。 いくつかのパッケージがJSON/JSONBフィールドを実装しています。 django-pgfields が、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')
-
あるいは他の NoSQL Django アダプタ -- それらを使えば、完全に動的なモデルを持つことができます。
NoSQLのDjangoライブラリは素晴らしいものですが、100%Djangoと互換性があるわけではないことに留意してください。 Django-nonrel 標準的なDjangoから、ManyToManyを リストフィールド などがあります。
この 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')] )
-
Django-mutantです。syncdb と South-hooks をベースにした動的なモデル
Django-mutant(ジャンゴミュータント は、完全に動的な外部キーと m2m フィールドを実装しています。また、以下のような素晴らしい、しかしややハック的なソリューションに触発されています。 ウィル・ハーディー やMichael Hallのような有名な作家の作品もあります。
これらはすべてDjango South hooksをベースにしており、それによると DjangoCon 2011 での Will Hardy の講演内容 (ご覧ください!) は、それにもかかわらず、堅牢で実運用でテストされている ( 関連するソースコード ).
そう、これはマジックです。これらのアプローチで、あなたは実現することができます。 完全に動的な Django アプリ、モデル、フィールド を、任意のリレーショナルデータベースバックエンドで使用することができます。しかし、どのような代償を払うことになるのでしょうか?多用するとアプリケーションの安定性が損なわれるのでは?これらは考慮しなければならない問題です。あなたは、適切な ロック を使用することで、同時にデータベースを変更するリクエストを許可することができます。
Michael Hallsのライブラリを使用する場合は、以下のようなコードになります。
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', )
関連
-
python string splicing.join()とsplitting.split()の説明
-
Python 可視化 big_screen ライブラリ サンプル 詳細
-
Pythonコードの可読性を向上させるツール「pycodestyle」の使い方を詳しく解説します
-
pyCaret効率化乗算器 オープンソース ローコード Python機械学習ツール
-
[解決済み】RuntimeWarning: invalid value encountered in double_scalars で numpy の除算ができない。
-
[解決済み】ValueError: xとyは同じサイズでなければならない
-
[解決済み】Djangoのクエリセットフィルタリングでnot equalを行うにはどうすればよいですか?
-
[解決済み] JSONをC#のダイナミックオブジェクトにデシリアライズする?
-
[解決済み] Django のカスタムフィールドで User モデルを拡張する
-
[解決済み】Djangoでnull=Trueとblank=Trueの違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Pythonによるjieba分割ライブラリ
-
python implement mysql add delete check change サンプルコード
-
Pythonの学習とデータマイニングのために知っておくべきターミナルコマンドのトップ10
-
Python 入出力と高次代入の基礎知識
-
[解決済み】ilocが「IndexError: single positional indexer is out-of-bounds」を出す。
-
[解決済み] データ型が理解できない
-
[解決済み】ImportError: PILという名前のモジュールがない
-
[解決済み】「SyntaxError.Syntax」は何ですか?Missing parentheses in call to 'print'」はPythonでどういう意味ですか?
-
[解決済み】Python: OverflowError: 数学の範囲エラー
-
[解決済み】django インポートエラー - core.managementという名前のモジュールがない