15


26

Djangoでの動的モデルフィールドの作成

これはdjangoに関する問題です。 「自動車」というモデルがあります。 これには、「Color」、「Vehicle Owner Name」、「Vehicle Cost」などの基本的なフィールドがあります。

ユーザーが追加する自動車に応じて、ユーザーが追加フィールドを追加できるフォームを提供したいと思います。 たとえば、ユーザーが「Car」を追加する場合、「Car Milage」、「Cal Manufacturer」など、実行時に動的にフォームにフィールドを追加します。 ユーザーが「トラック」を追加する場合、「持ち運び可能な荷物」、「許可」などを追加するとします。

ジャンゴでこれを達成するにはどうすればよいですか?

ここに2つの質問があります。

  1. ユーザーが実行時に新しいフィールドを追加できるフォームを提供する方法は?

  2. できるようにデータベースにフィールドを追加する方法 後で取得/照会しましたか?

4 Answer


26


いくつかのアプローチがあります:

  • キー/値モデル(簡単、十分にサポート)

  • TextField内のJSONデータ(簡単、柔軟、簡単に検索/インデックス付けできない)

  • 動的モデル定義(それほど簡単ではない、多くの隠れた問題)

あなたは最後のものが欲しいように聞こえますが、それがあなたにとって最適かどうかはわかりません。 Djangoの変更/更新は非常に簡単です。システム管理者が追加のフィールドが必要な場合は、それらを追加して、southを使用して移行します。 汎用キー/値データベーススキーマが好きではありません。Djangoのような強力なフレームワークのポイントは、汎用アプローチに頼らずにカスタムスキーマを簡単に記述および書き換えできることです。

サイトのユーザー/管理者が自分のデータを直接定義できるようにする必要がある場合は、上記の最初の2つのアプローチを実行する方法を他の人が紹介するはずです。 3番目のアプローチは、あなたが求めていたものであり、もう少しおかしくなりました。その方法を紹介します。 ほとんどすべての場合に使用することはお勧めしませんが、適切な場合もあります。

動的モデル

何をすべきかがわかれば、これは比較的簡単です。 あなたは必要になるでしょう:

  • フィールドの名前とタイプを保存する1つまたは2つのモデル

  • (オプション)一般的な機能を定義するための抽象モデル (サブクラス化)動的モデル

  • 必要なときに動的モデルを構築(または再構築)する機能

  • フィールドが存在するときにデータベーステーブルを構築または更新するコード 追加/削除/名前変更

1. モデル定義の保存

これはあなた次第です。 モデル CustomCarModel`と CustomField`を使用して、ユーザー/管理者が必要なフィールドの名前とタイプを定義および保存できるようになると思います。 Djangoフィールドを直接ミラーリングする必要はありません。ユーザーがよりよく理解できる独自のタイプを作成できます。

インラインフォームセットで `forms.ModelForm`を使用して、ユーザーがカスタムクラスを作成できるようにします。

2. 抽象モデル

繰り返しますが、これは簡単です。すべての動的モデルに共通のフィールド/メソッドを使用して基本モデルを作成するだけです。 このモデルを抽象化します。

3. 動的モデルを作成する

必要な情報(おそらく#1のクラスのインスタンス)を取得し、モデルクラスを生成する関数を定義します。 これは基本的な例です:

from django.db.models.loading import cache
from django.db import models


def get_custom_car_model(car_model_definition):
  """ Create a custom (dynamic) model class based on the given definition.
  """
  # What's the name of your app?
  _app_label = 'myapp'

  # you need to come up with a unique table name
  _db_table = 'dynamic_car_%d' % car_model_definition.pk

  # you need to come up with a unique model name (used in model caching)
  _model_name = "DynamicCar%d" % car_model_definition.pk

  # Remove any exist model definition from Django's cache
  try:
    del cache.app_models[_app_label][_model_name.lower()]
  except KeyError:
    pass

  # We'll build the class attributes here
  attrs = {}

  # Store a link to the definition for convenience
  attrs['car_model_definition'] = car_model_definition

  # Create the relevant meta information
  class Meta:
      app_label = _app_label
      db_table = _db_table
      managed = False
      verbose_name = 'Dynamic Car %s' % car_model_definition
      verbose_name_plural = 'Dynamic Cars for %s' % car_model_definition
      ordering = ('my_field',)
  attrs['__module__'] = 'path.to.your.apps.module'
  attrs['Meta'] = Meta

  # All of that was just getting the class ready, here is the magic
  # Build your model by adding django database Field subclasses to the attrs dict
  # What this looks like depends on how you store the users's definitions
  # For now, I'll just make them all CharFields
  for field in car_model_definition.fields.all():
    attrs[field.name] = models.CharField(max_length=50, db_index=True)

  # Create the new model class
  model_class = type(_model_name, (CustomCarModelBase,), attrs)

  return model_class

4. データベーステーブルを更新するコード

上記のコードは動的なモデルを生成しますが、データベーステーブルは作成しません。 テーブル操作にはSouthを使用することをお勧めします。 以下に、プリ/ポスト保存信号に接続できるいくつかの機能を示します。

import logging
from south.db import db
from django.db import connection

def create_db_table(model_class):
  """ Takes a Django model class and create a database table, if necessary.
  """
  table_name = model_class._meta.db_table
  if (connection.introspection.table_name_converter(table_name)
                    not in connection.introspection.table_names()):
    fields = [(f.name, f) for f in model_class._meta.fields]
    db.create_table(table_name, fields)
    logging.debug("Creating table '%s'" % table_name)

def add_necessary_db_columns(model_class):
  """ Creates new table or relevant columns as necessary based on the model_class.
    No columns or data are renamed or removed.
    XXX: May need tweaking if db_column != field.name
  """
  # Create table if missing
  create_db_table(model_class)

  # Add field columns if missing
  table_name = model_class._meta.db_table
  fields = [(f.column, f) for f in model_class._meta.fields]
  db_column_names = [row[0] for row in connection.introspection.get_table_description(connection.cursor(), table_name)]

  for column_name, field in fields:
    if column_name not in db_column_names:
      logging.debug("Adding field '%s' to table '%s'" % (column_name, table_name))
      db.add_column(table_name, column_name, field)

そして、あなたはそれを持っています! `get_custom_car_model()`を呼び出して、通常のdjangoクエリを実行するために使用できるdjangoモデルを提供できます。

CarModel = get_custom_car_model(my_definition)
CarModel.objects.all()

問題

  • モデルは、モデルを作成するコードが run. ただし、定義モデルの「class_prepared」シグナルの定義のすべてのインスタンスに対して「get_custom_car_model」を実行できます。

  • ForeignKeys /` ManyToManyFields`は動作しない可能性があります(試したことはありません)

  • Djangoのモデルキャッシュを使用すると、実行する必要がなくなります。 これを使用するたびにクエリを実行し、モデルを作成します。 簡単にするためにこれを省略しました

  • 動的モデルを管理者に取り込むことができますが、必要です adminクラスも動的に作成し、シグナルを使用して適切に登録/再登録/登録解除します。

概要

追加された合併症や問題に満足している場合は、お楽しみください! 実行中の1つは、DjangoとPythonの柔軟性のおかげで、期待どおりに機能します。 モデルをDjangoの `ModelForm`にフィードして、ユーザーがインスタンスを編集し、データベースのフィールドを直接使用してクエリを実行できるようにすることができます。 上記でわからないことがある場合は、おそらくこのアプローチを取らないことをお勧めします(初心者のために概念の一部を説明していません)。 複雑にしないでおく!

多くの人がこれを必要とは思わないが、私はそれを自分で使った。テーブルにたくさんのデータがあり、実際にユーザーが列をカスタマイズできるようにする必要があった。


8


データベース

データベース設計をもう一度検討してください。

表現したいオブジェクトが実際の世界で互いにどのように関係しているかを考えてから、できる限りそれらの関係を一般化するように努める必要があります(したがって、各トラックに許可があると言う代わりに、各車両と言います許可、負荷量などのいずれかの属性を持つことができます)。

試してみましょう:

車両があり、各車両が多くのユーザー指定の属性を持つことができる場合、次のモデルを検討してください。

class Attribute(models.Model):
    type  = models.CharField()
    value = models.CharField()

class Vehicle(models.Model):
    attribute = models.ManyToMany(Attribute)

前述のとおり、これは一般的な考え方であり、各車両に必要なだけ属性を追加できます。

特定の属性セットをユーザーが利用できるようにするには、http://docs.djangoproject.com/en/dev/ref/models/instances/?from = olddocs#django.db.models.Model.get_FOO_displayを使用できます[Attribute.type]フィールドの[choices]。

ATTRIBUTE_CHOICES = (
    (1, 'Permit'),
    (2, 'Manufacturer'),
)
class Attribute(models.Model):
    type = models.CharField(max_length=1, choices=ATTRIBUTE_CHOICES)
    value = models.CharField()

ここで、おそらく、各車両に独自の利用可能な属性セットを持たせたいと思うでしょう。 これは、さらに別のモデルを追加し、「Vehicle」モデルと「Attribute」モデルの両方から外部キー関係を設定することで実行できます。

class VehicleType(models.Model):
    name  = models.CharField()

class Attribute(models.Model):
    vehicle_type = models.ForeigngKey(VehicleType)
    type  = models.CharField()
    value = models.CharField()

class Vehicle(models.Model):
    vehicle_type = models.ForeigngKey(VehicleType)
    attribute = models.ManyToMany(Attribute)

このようにして、各属性が特定の車両にどのように関連するかを明確に把握できます。

フォーム

基本的に、このデータベース設計では、データベースにオブジェクトを追加するために2つのフォームが必要です。 具体的には、車両のhttp://docs.djangoproject.com/en/dev/topics/forms/modelforms/[model form]およびhttp://docs.djangoproject.com/en/dev/topics/forms/modelforms /#model-formsets [model formset]属性について。 jQueryを使用して、 `Attribute`フォームセットにさらに項目を動的に追加できます。

'' '' '

注意

また、 Attribute`クラスを AttributeType`と `AttributeValue`に分けて、データベースに冗長な属性タイプを保存しないようにするか、ユーザーの属性選択を制限したいが、さらにタイプを追加できるようにします。 Django管理サイト。

完全にクールにするために、フォームでオートコンプリートを使用して、既存の属性タイプをユーザーに提案できます。

ヒント:http://en.wikipedia.org/wiki/Database_normalization [データベースの正規化]の詳細をご覧ください。

'' '' '

その他のソリューション

Stuart Marshによる以前の回答で示唆されたとおり

一方、各車両タイプがベース車両のサブクラスで表され、各サブクラスが独自の特定の属性を持つことができるように、各車両タイプのモデルをハードコーディングできますが、ソリューションは非常に柔軟ではありません(柔軟性が必要な場合) 。

追加のオブジェクト属性のJSON表現を1つのデータベースフィールドに保持することもできますが、属性を照会するときにこれが役立つかどうかはわかりません。


2


ここにdjangoシェルでの簡単なテストがあります-入力したばかりで、うまくいくようです-

    In [25]: attributes = {
               "__module__": "lekhoni.models",
               "name": models.CharField(max_length=100),
                "address":  models.CharField(max_length=100),
            }

    In [26]: Person = type('Person', (models.Model,), attributes)

    In [27]: Person
    Out[27]: class 'lekhoni.models.Person'

    In [28]: p1= Person()

    In [29]: p1.name= 'manir'

    In [30]: p1.save()

    In [31]: Person.objects.a
    Person.objects.aggregate  Person.objects.all        Person.objects.annotate

    In [32]: Person.objects.all()

    Out[33]: [Person: Person object]

非常に単純なようです-なぜそれがオプションと見なされるべきではないのかわかりません-リフレクションはC#やJavaのような他の言語で非常に一般的です


1


あなたはフロントエンドのインターフェースで話しているのですか、それともDjangoの管理者ですか?

フードの下で多くの作業をせずに、その場で実際のフィールドを作成することはできません。 Djangoの各モデルとフィールドには、データベース内のテーブルと列が関連付けられています。 通常、新しいフィールドを追加するには、生のSQL、またはSouthを使用した移行が必要です。

フロントエンドインターフェイスから、擬似フィールドを作成し、単一のモデルフィールドにJSON形式で保存できます。

たとえば、モデルにother_dataテキストフィールドを作成します。 次に、ユーザーがフィールドを作成できるようにし、\ {'userfield': 'userdata'、 'mileage':54}のように保存します。

しかし、車両のような有限クラスを使用している場合、基本的な車両特性を備えた基本モデルを作成し、次に各車両タイプの基本モデルから継承するモデルを作成すると思います。

class base_vehicle(models.Model):
    color = models.CharField()
    owner_name = models.CharField()
    cost = models.DecimalField()

class car(base_vehicle):
    mileage = models.IntegerField(default=0)

etc