Django에서 OneToOneField가 None인지 확인하십시오.


85

다음과 같은 두 가지 모델이 있습니다.

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.

사용자의 프로필 유형을 어떻게 확인할 수 있습니까?

감사

답변:


92

(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
class stacker

7
파이썬 <3.2, 참고 hasattr삼켜 모든 데이터베이스 조회시 발생 예외를뿐 아니라 DoesNotExist. 이것은 아마도 당신이 원하는 것이 아니라 고장난 것입니다.
Pi Delport 2013 년

파이썬 2.7에서 작동하지 않습니다. OneToOne이 존재하지 않더라도 django.db.models.fields.related.RelatedManager 객체를 반환합니다.
alexpirine 2013

@alartur 어떤 django 버전을 사용하고 있습니까?
joctee

장고 1.5. 그러나 저는 제가하고 싶은 일을 완전히 다른 방식으로 구현함으로써 저의 특정한 문제를 해결했습니다.
alexpirine 2013

48

단순히 모델의 해당 필드를 테스트하여 특정 모델에 대해 nullable 일대일 관계가 null 인지 확인할 수 None있지만, 일대일 관계가 시작된 모델에서 테스트하는 경우 에만 가능합니다. 예를 들어,이 두 클래스가 주어지면…

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가 있는지 확인하려면 PlaceA가 들어 Restaurant, 그것을 참조하는 것을 이해하는 것이 중요 restaurant의 인스턴스에 속성을 Place높이는 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.

이 시나리오에서는 Occam의 면도기가 우세하며 여기에 설명 된대로 a Place가 있는지 여부를 결정하는 가장 좋은 방법 Restautrant은 표준 try/ except구조 입니다.

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

hasattr실제로 작업 을 사용하라는 joctee의 제안 은 실제로는 s가 아닌 모든 예외 (포함 )를 hasattr억제 하므로 우연히 만 작동 합니다. 으로 파이 Delport가 : 지적,이 동작은 실제로는 다음 표에 따라 파이썬 3.2에서 수정되었습니다 http://bugs.python.org/issue9666 . 또한-그리고 의견이있는 것처럼 들릴 위험이 있습니다-위의 / 구조가 장고가 작동하는 방식을 더 잘 대표 한다고 생각하지만 , 사용 하면 FUD를 생성하고 나쁜 습관을 퍼뜨릴 수있는 초보자를위한 문제가 흐려질 수 있습니다.DoesNotExistAttributeErrortryexcepthasattr

편집 Don Kirkby의 합리적인 타협도 나에게 합리적으로 보입니다.


19

나는 Joctee의 대답을 좋아 합니다. 왜냐하면 너무 간단하기 때문입니다.

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

다른 댓글 작성자는 특정 버전의 Python 또는 Django에서 작동하지 않을 수 있다는 우려를 제기했지만 Django 문서 는이 기술을 옵션 중 하나로 보여줍니다.

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.

나는 예외를 잡으면 무슨 일이 일어나고 있는지 더 명확하게 만든다는 Joshua의 의견에 동의 하지만, 나에게는 더 지저분 해 보입니다. 아마도 이것은 합리적인 타협입니까?

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

이것은 Restaurant장소별로 객체를 쿼리하는 것입니다. None그 장소에 식당이 없으면 반환 됩니다.

다음은 옵션을 사용할 수있는 실행 가능한 스 니펫입니다. Python, Django 및 SQLite3가 설치되어있는 경우 실행하면됩니다. Python 2.7, Python 3.4, Django 1.9.2 및 SQLite3 3.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 / except 블록을 사용하는 것은 어떻습니까?

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의이 동작이 실제로 문서화되어 있습니까?
Kos

3
방금 Django 1.9.2에서 시도했는데 RelatedObjectDoesNotExist.
Don Kirkby

1

모델이있는 경우

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

그리고 UserProfile이 존재하거나 존재하지 않는다는 것을 알고 있으면 데이터베이스 관점에서 존재 쿼리 를 사용 하는 가장 효율적인 방법 입니다 .

쿼리 그냥 부울이 아닌 리턴 존재 역 속성 액세스를 같이 hasattr(request.user, 'type1profile')생성되는 - GET 쿼리를 전체 객체 표현을 반환

그렇게하려면 사용자 모델에 속성을 추가해야합니다.

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

현명한 접근 방법 중 하나는 사용자 정의 필드 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대신 사용해야 ReverseOneToOneDescriptor합니다 from django.db.models.fields.related import SingleRelatedObjectDescriptor
pymen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.