web-dev-qa-db-ja.com

プロパティはDjangoモデルフィールドで機能しますか?

この質問をする最良の方法はいくつかのコードを使用することだと思います...これを行うことができますか? (編集:答え:いいえ)

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」として保持できます。これは、既存のシステムで作業していて、理由もなくデータベースの移行を行う必要がない場合に非常に役立ちます。

38
Jiaaro

モデルフィールドは既にプロパティであるため、名前の競合を回避するには2番目の方法で行う必要があります。

foo = property(..)を定義すると、実際にはfoo = models..行をオーバーライドするため、そのフィールドにはアクセスできなくなります。

プロパティとフィールドには別の名前を使用する必要があります。実際、例1のようにそれを行うと、プロパティにアクセスしようとすると無限ループになり、プロパティが自分自身を返すようになります。

編集:fooではプロパティを使用できないため、フィールド名として_fooではなくQuerySetを使用することを検討し、プロパティに別の名前を定義することも検討してください。たとえば、フィルターを実行する場合は、実際のフィールド名を使用する必要があります。

20
Andre Miller

前述のように、独自の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__foodir(..)で見られるように)に解決されるため、非表示になります( private )。このフォームでは @ property decorator の使用も許可されていることに注意してください。これは、最終的には読み取り可能なコードを記述するためのより良い方法です。

繰り返しますが、Djangoは、2つのフィールドfooおよびbarを持つ* _MyModelテーブルを作成します。

18

@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 __

6

propertyが目的を達成するための手段であるか、それ自体が目的であるかによって異なります。

(最初にそれらを評価する必要なしに)クエリセットをフィルタリングするときにこの種の「オーバーライド」(または「フォールバック」)動作が必要な場合、プロパティがうまくいくとは思えません。 私の知る限り 、Pythonプロパティはデータベースレベルでは機能しないため、クエリセットフィルターでは使用できません。注意してくださいcanフィルタで__foo_を使用します(fooではなく)。これは実際のテーブル列を表しますが、get_foo()からのオーバーライドロジック適用されません。

ただし、ユースケースで許可されている場合は、_Django.db.models.functions_( docs )のCoalesce()クラスが役立つ場合があります。

Coalesce() ...少なくとも2つのフィールド名または式のリストを受け入れ、最初の非null値を返します(空の文字列はnull値とは見なされないことに注意してください)。 ...

これは、Coalesce('bar','foo')を使用してbarfooのオーバーライドとして指定できることを意味します。 barbarである場合を除き、これは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 lookupcustom(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の例 )。

1
djvg