この質問をする最良の方法はいくつかのコードを使用することだと思います...これを行うことができますか? (編集:答え:いいえ)
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self.foo
def set_foo(self, input):
self.foo = input
foo = property(get_foo, set_foo)
または私はこのようにする必要がありますか:
class MyModel(models.Model):
_foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
def get_foo(self):
if self.bar:
return self.bar
else:
return self._foo
def set_foo(self, input):
self._foo = input
foo = property(get_foo, set_foo)
note:db_columnをモデルフィールドに渡すことにより、データベースで列名を「foo」として保持できます。これは、既存のシステムで作業していて、理由もなくデータベースの移行を行う必要がない場合に非常に役立ちます。
モデルフィールドは既にプロパティであるため、名前の競合を回避するには2番目の方法で行う必要があります。
foo = property(..)
を定義すると、実際にはfoo = models..
行をオーバーライドするため、そのフィールドにはアクセスできなくなります。
プロパティとフィールドには別の名前を使用する必要があります。実際、例1のようにそれを行うと、プロパティにアクセスしようとすると無限ループになり、プロパティが自分自身を返すようになります。
編集:foo
ではプロパティを使用できないため、フィールド名として_foo
ではなくQuerySet
を使用することを検討し、プロパティに別の名前を定義することも検討してください。たとえば、フィルターを実行する場合は、実際のフィールド名を使用する必要があります。
前述のように、独自のDjango.db.models.Fieldクラスを実装するための正しい代替手段として、-db_column引数とカスタム(または非表示)を使用する必要がありますクラス属性。 OOP in python)のより厳密な規則に従って、@ Jiaaroによる編集でコードを書き直しています(たとえば、_fooを実際に非表示にする必要がある場合):
class MyModel(models.Model):
__foo = models.CharField(max_length = 20, db_column='foo')
bar = models.CharField(max_length = 20)
@property
def foo(self):
if self.bar:
return self.bar
else:
return self.__foo
@foo.setter
def foo(self, value):
self.__foo = value
__ fooは_ MyModel__foo(dir(..)で見られるように)に解決されるため、非表示になります( private )。このフォームでは @ property decorator の使用も許可されていることに注意してください。これは、最終的には読み取り可能なコードを記述するためのより良い方法です。
繰り返しますが、Djangoは、2つのフィールドfooおよびbarを持つ* _MyModelテーブルを作成します。
@propertyがadminと.filter(_foo)で問題を引き起こすため、以前のソリューションは影響を受けます。
より良い解決策はsetattrをオーバーライドすることです。ただし、DBからORMオブジェクトを初期化するときに問題が発生する可能性があります。しかし、これを回避するトリックがあり、それは普遍的です。
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def __setattr__(self, attrname, val):
setter_func = 'setter_' + attrname
if attrname in self.__dict__ and callable(getattr(self, setter_func, None)):
super(MyModel, self).__setattr__(attrname, getattr(self, setter_func)(val))
else:
super(MyModel, self).__setattr__(attrname, val)
def setter_foo(self, val):
return val.upper()
秘密は 'attrname in self .__ dict __'です。モデルが初期化されたとき、または__ dict __!
property
が目的を達成するための手段であるか、それ自体が目的であるかによって異なります。
(最初にそれらを評価する必要なしに)クエリセットをフィルタリングするときにこの種の「オーバーライド」(または「フォールバック」)動作が必要な場合、プロパティがうまくいくとは思えません。 私の知る限り 、Pythonプロパティはデータベースレベルでは機能しないため、クエリセットフィルターでは使用できません。注意してくださいcanフィルタで__foo
_を使用します(foo
ではなく)。これは実際のテーブル列を表しますが、get_foo()
からのオーバーライドロジック適用されません。
ただし、ユースケースで許可されている場合は、_Django.db.models.functions
_( docs )のCoalesce()
クラスが役立つ場合があります。
Coalesce()
...少なくとも2つのフィールド名または式のリストを受け入れ、最初の非null値を返します(空の文字列はnull値とは見なされないことに注意してください)。 ...
これは、Coalesce('bar','foo')
を使用してbar
をfoo
のオーバーライドとして指定できることを意味します。 bar
がbar
である場合を除き、これはnull
を返します。この場合、foo
が返されます。 get_foo()
と同じですが(空の文字列では機能しない点を除きます)、データベースレベルでは
残っている問題は、これをどのように実装するかです。
多くの場所で使用しない場合は、単純に annotating クエリセットが最も簡単です。あなたの例を使用して、プロパティのものなしで:
_class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
_
次に、次のようなクエリを作成します。
_from Django.db.models.functions import Coalesce
queryset = MyModel.objects.annotate(bar_otherwise_foo=Coalesce('bar', 'foo'))
_
これで、クエリセット内のアイテムに_bar_otherwise_foo
_というマジック属性があり、これをフィルターに掛けることができます。 queryset.filter(bar_otherwise_foo='what I want')
、またはインスタンスで直接使用できます。 print(queryset.all()[0].bar_otherwise_foo)
_queryset.query
_から得られる SQLクエリ は、Coalesce()
が実際にデータベースレベルで機能することを示しています。
_SELECT "myapp_mymodel"."id", "myapp_mymodel"."foo", "myapp_mymodel"."bar",
COALESCE("myapp_mymodel"."bar", "myapp_mymodel"."foo") AS "bar_otherwise_foo"
FROM "myapp_mymodel"
_
注:モデルフィールドを__foo
_、次にfoo=Coalesce('bar', '_foo')
などと呼ぶこともできます。foo=Coalesce('bar', 'foo')
を使用するのは魅力的ですが、_ValueError: The annotation 'foo' conflicts with a field on the model.
_が発生します。
DRY実装を作成するには、いくつかの方法が必要です。たとえば、 custom lookup や custom(ized)Manager を記述します。
カスタムマネージャーは次のように簡単に実装できます( ドキュメントの例 を参照)。
_class MyModelManager(models.Manager):
""" standard manager with customized initial queryset """
def get_queryset(self):
return super(MyModelManager, self).get_queryset().annotate(
bar_otherwise_foo=Coalesce('bar', 'foo'))
class MyModel(models.Model):
objects = MyModelManager()
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
_
これで、MyModel
のすべてのクエリセットに_bar_otherwise_foo
_アノテーションが自動的に追加され、上記のように使用できます。
ただし、たとえばインスタンスでbar
を更新しても、クエリセットで行われたため、注釈は更新されません。クエリセットは最初に再評価する必要があります。クエリセットから更新されたインスタンスを取得する。
おそらく、カスタムマネージャーとアノテーションの組み合わせとPython property
を使用して、両方の世界を最大限に活用できます( CodeReviewの例 )。