DjangoでOneToOneFieldがNoneかどうかを確認します


86

私はこのような2つのモデルを持っています:

class Type1Profile(models.Model):
    user = models.OneToOneField(User, unique=True)
    ...


class Type2Profile(models.Model):
    user = models.OneToOneField(User, unique=True)
    ...

ユーザーがType1またはType2プロファイルを持っている場合は、何かをする必要があります。

if request.user.type1profile != None:
    # do something
elif request.user.type2profile != None:
    # do something else
else:
    # do something else

ただし、type1またはtype2プロファイルを持たないユーザーの場合、そのようなコードを実行すると、次のエラーが発生します。

Type1Profile matching query does not exist.

ユーザーが持っているプロファイルのタイプを確認するにはどうすればよいですか?

ありがとう

回答:


93

(OneToOne)関係が存在するかどうかを確認するには、次のhasattr関数を使用できます。

if hasattr(request.user, 'type1profile'):
    # do something
elif hasattr(request.user, 'type2profile'):
    # do something else
else:
    # do something else

4
この解決策をありがとう。残念ながら、これは常に機能するとは限りません。場合にはあなたが仕事をしたいselect_related()現在または将来における-または多分あなたはまた別の場所で発生する可能性があり、魔法の他の種類扱うように-あなたは次のようにテストを拡張する必要があります:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
クラススタッカ

7
Python <3.2では、データベースのルックアップ中に発生するすべての例外をhasattr飲み込むことに注意してください。これはおそらく壊れており、あなたが望むものではありません。DoesNotExist
Pi Delport 2013年

Python2.7では動作しません。OneToOneが存在しない場合でも、django.db.models.fields.related.RelatedManagerオブジェクトを返します。
アレクスピリン2013

@alarturどのdjangoバージョンを使用していますか?
joctee 2013

Django1.5。しかし、私は自分がやりたいことをまったく異なる方法で実装することで、特定の問題を解決しました。
アレクスピリン2013

48

これは、NULL可能一対一の関係がnullのは、単純にするためのモデルに対応するフィールドをテストすることによって、特定のモデルのためであるかどうかを確認することが可能ですNoneネス、しかし、唯一のあなたは、モデルの1対1の関係の発信元でテスト場合。たとえば、これら2つのクラスが与えられた場合…

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(models.Model):  # The class where the one-to-one originates
    place = models.OneToOneField(Place, blank=True, null=True)
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()

…aにRestaurantが含まれているかどうかPlaceを確認するには、次のコードを使用できます。

>>> r = Restaurant(serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
>>> if r.place is None:
>>>    print "Restaurant has no place!"
Restaurant has no place!

aにPlaceがあるかどうかを確認するには、のインスタンスでプロパティRestaurantを参照すると、対応するレストランがない場合に例外restaurantPlace発生することを理解することが重要Restaurant.DoesNotExistです。これは、Djangoがを使用して内部的にルックアップを実行するために発生しQuerySet.get()ます。例えば:

>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
>>> p2.restaurant
Traceback (most recent call last):
    ...
DoesNotExist: Restaurant matching query does not exist.

このシナリオでは、オッカムの剃刀が優勢であり、Placeが持っているかどうかを判断するための最良のアプローチは、ここで説明Restautrantする標準try/except構成です

>>> try:
>>>     restaurant = p2.restaurant
>>> except Restaurant.DoesNotExist:
>>>     print "Place has no restaurant!"
>>> else:
>>>     # Do something with p2's restaurant here.

jocteeの提案hasattrは実際には機能しますが、実際には偶然にしか機能しません。これは、sだけではなく、すべての例外(を含む)をhasattr抑制しているためです。以下のようパイにdelportは:指摘、この動作は実際には、以下のチケットごとのPython 3.2で修正されましたhttp://bugs.python.org/issue9666。さらに、そして意見が分かれるように聞こえるリスクがありますが、上記の/構成はDjangoの動作をよりよく表していると思いますが、使用すると初心者の問題が曇ってしまい、FUDが発生して悪い習慣が広がる可能性があります。DoesNotExistAttributeErrortryexcepthasattr

編集 ドン・カークビーの合理的な妥協も私には合理的だと思います。


19

とてもシンプルなので、ジョッティーの答えが好きです

if hasattr(request.user, 'type1profile'):
    # do something
elif hasattr(request.user, 'type2profile'):
    # do something else
else:
    # do something else

他のコメント提供者は、PythonまたはDjangoの特定のバージョンでは機能しない可能性があるという懸念を提起しましたが、Djangoのドキュメントには、この手法がオプションの1つとして示されています。

hasattrを使用して、例外キャッチの必要性を回避することもできます。

>>> hasattr(p2, 'restaurant')
False

もちろん、ドキュメントには例外キャッチ手法も示されています。

p2には関連するレストランがありません:

>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>>     p2.restaurant
>>> except ObjectDoesNotExist:
>>>     print("There is no restaurant here.")
There is no restaurant here.

例外を捕まえることで何が起こっているのかが明確になるというジョシュアに同意しますが、それは私には厄介に思えます。おそらくこれは合理的な妥協案ですか?

>>> print(Restaurant.objects.filter(place=p2).first())
None

これはRestaurant、場所ごとにオブジェクトをクエリするだけです。Noneその場所にレストランがない場合は戻ります。

これは、オプションで遊ぶための実行可能なスニペットです。Python、Django、SQLite3がインストールされている場合は、実行する必要があります。Python 2.7、Python 3.4、Django 1.9.2、SQLite33.8.2でテストしました。

# Tested with Django 1.9.2
import sys

import django
from django.apps import apps
from django.apps.config import AppConfig
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, models, DEFAULT_DB_ALIAS
from django.db.models.base import ModelBase

NAME = 'udjango'


def main():
    setup()

    class Place(models.Model):
        name = models.CharField(max_length=50)
        address = models.CharField(max_length=80)

        def __str__(self):              # __unicode__ on Python 2
            return "%s the place" % self.name

    class Restaurant(models.Model):
        place = models.OneToOneField(Place, primary_key=True)
        serves_hot_dogs = models.BooleanField(default=False)
        serves_pizza = models.BooleanField(default=False)

        def __str__(self):              # __unicode__ on Python 2
            return "%s the restaurant" % self.place.name

    class Waiter(models.Model):
        restaurant = models.ForeignKey(Restaurant)
        name = models.CharField(max_length=50)

        def __str__(self):              # __unicode__ on Python 2
            return "%s the waiter at %s" % (self.name, self.restaurant)

    syncdb(Place)
    syncdb(Restaurant)
    syncdb(Waiter)

    p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
    p1.save()
    p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
    p2.save()
    r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
    r.save()

    print(r.place)
    print(p1.restaurant)

    # Option 1: try/except
    try:
        print(p2.restaurant)
    except ObjectDoesNotExist:
        print("There is no restaurant here.")

    # Option 2: getattr and hasattr
    print(getattr(p2, 'restaurant', 'There is no restaurant attribute.'))
    if hasattr(p2, 'restaurant'):
        print('Restaurant found by hasattr().')
    else:
        print('Restaurant not found by hasattr().')

    # Option 3: a query
    print(Restaurant.objects.filter(place=p2).first())


def setup():
    DB_FILE = NAME + '.db'
    with open(DB_FILE, 'w'):
        pass  # wipe the database
    settings.configure(
        DEBUG=True,
        DATABASES={
            DEFAULT_DB_ALIAS: {
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': DB_FILE}},
        LOGGING={'version': 1,
                 'disable_existing_loggers': False,
                 'formatters': {
                    'debug': {
                        'format': '%(asctime)s[%(levelname)s]'
                                  '%(name)s.%(funcName)s(): %(message)s',
                        'datefmt': '%Y-%m-%d %H:%M:%S'}},
                 'handlers': {
                    'console': {
                        'level': 'DEBUG',
                        'class': 'logging.StreamHandler',
                        'formatter': 'debug'}},
                 'root': {
                    'handlers': ['console'],
                    'level': 'WARN'},
                 'loggers': {
                    "django.db": {"level": "WARN"}}})
    app_config = AppConfig(NAME, sys.modules['__main__'])
    apps.populate([app_config])
    django.setup()
    original_new_func = ModelBase.__new__

    @staticmethod
    def patched_new(cls, name, bases, attrs):
        if 'Meta' not in attrs:
            class Meta:
                app_label = NAME
            attrs['Meta'] = Meta
        return original_new_func(cls, name, bases, attrs)
    ModelBase.__new__ = patched_new


def syncdb(model):
    """ Standard syncdb expects models to be in reliable locations.

    Based on https://github.com/django/django/blob/1.9.3
    /django/core/management/commands/migrate.py#L285
    """
    connection = connections[DEFAULT_DB_ALIAS]
    with connection.schema_editor() as editor:
        editor.create_model(model)

main()

10

try / exceptionブロックを使用するのはどうですか?

def get_profile_or_none(user, profile_cls):

    try:
        profile = getattr(user, profile_cls.__name__.lower())
    except profile_cls.DoesNotExist:
        profile = None

    return profile

そして、このように使用してください!

u = request.user
if get_profile_or_none(u, Type1Profile) is not None:
    # do something
elif get_profile_or_none(u, Type2Profile) is not None:
    # do something else
else:
    # d'oh!

これをジェネリック関数として使用して、元のクラス(ここではプロファイルクラス)と関連するインスタンス(ここではrequest.user)を指定して、逆OneToOneインスタンスを取得できると思います。


3

使用してくださいselect_related

>>> user = User.objects.select_related('type1profile').get(pk=111)
>>> user.type1profile
None

2
私はそれがこのように機能することを知っていますが、select_relatedのこの動作は実際に文書化されていますか?
コス

3
Django 1.9.2でこれを試したところ、が発生しRelatedObjectDoesNotExistます。
ドンカークビー2016年

1

あなたがモデルを持っている場合

class UserProfile(models.Model):
    user = models.OneToOneField(User, unique=True)

また、UserProfileが存在するかどうかをユーザーが知る必要があります。データベースの観点から最も効率的な方法はexistsqueryです。

Existsクエリは、-のような逆属性アクセスではなく、ブール値のみを返します。これによりgetクエリhasattr(request.user, 'type1profile')が生成され完全なオブジェクト表現が返されます。

これを行うには、ユーザーモデルにプロパティを追加する必要があります

class User(AbstractBaseUser)

@property
def has_profile():
    return UserProfile.objects.filter(user=self.pk).exists()

0

私はhas_attrの組み合わせを使用していて、Noneです:

class DriverLocation(models.Model):
    driver = models.OneToOneField(Driver, related_name='location', on_delete=models.CASCADE)

class Driver(models.Model):
    pass

    @property
    def has_location(self):
        return not hasattr(self, "location") or self.location is None

0

スマートなアプローチの1つは、カスタムフィールドOneToOneOrNoneFieldを追加して使用することです[Djangoで機能> = 1.9]

from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor
from django.core.exceptions import ObjectDoesNotExist
from django.db import models


class SingleRelatedObjectDescriptorReturnsNone(ReverseOneToOneDescriptor):
    def __get__(self, *args, **kwargs):
        try:
            return super().__get__(*args, **kwargs)
        except ObjectDoesNotExist:
            return None


class OneToOneOrNoneField(models.OneToOneField):
    """A OneToOneField that returns None if the related object doesn't exist"""
    related_accessor_class = SingleRelatedObjectDescriptorReturnsNone

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('null', True)
        kwargs.setdefault('blank', True)
        super().__init__(*args, **kwargs)

実装

class Restaurant(models.Model):  # The class where the one-to-one originates
    place = OneToOneOrNoneField(Place)
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()

使用法

r = Restaurant(serves_hot_dogs=True, serves_pizza=False)
r.place  # will return None

django 1.8の場合、このような SingleRelatedObjectDescriptor代わりに使用する必要がありますReverseOneToOneDescriptorfrom django.db.models.fields.related import SingleRelatedObjectDescriptor
pymen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.