【问题标题】:Check if OneToOneField is None in Django检查 Django 中的 OneToOneField 是否为 None
【发布时间】:2011-03-28 15:42:12
【问题描述】:

我有两个这样的模型:

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.

如何查看用户的个人资料类型?

谢谢

【问题讨论】:

    标签: python django django-models one-to-one


    【解决方案1】:

    要检查(OneToOne)关系是否存在,可以使用hasattr函数:

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

    【讨论】:

    • 感谢您提供此解决方案。不幸的是,这并不总是有效。如果您现在或将来想与select_related() 一起工作——或者甚至是为了确保您还可以处理可能在其他地方发生的其他类型的魔法——您必须将测试扩展如下:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None跨度>
    • 请注意,在 Python hasattr 将吞噬数据库查找期间发生的所有异常,而不仅仅是 DoesNotExist。这可能是坏的,而不是你想要的。
    • 不适用于 python 2.7。即使 OneToOne 不存在,它也会返回一个 django.db.models.fields.related.RelatedManager 对象。
    • @alartur 你用的是什么 django 版本?
    • Django 1.5。但我通过以完全不同的方式实现我想做的事情解决了我的特定问题。
    【解决方案2】:

    可以通过测试模型上Noneness 的相应字段来查看特定模型的可为空的一对一关系是否为空,但如果您测试一对一关系起源的模型。例如,给定这两个类……

    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()
    

    ...查看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!
    

    要查看Place 是否有Restaurant,请务必了解,如果没有相应的餐厅,则在Place 的实例上引用restaurant 属性会引发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 构造,如here 所述。

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

    虽然 joctee 使用 hasattr 的建议在实践中有效,但它实际上只是偶然有效,因为 hasattr 抑制了所有异常(包括 DoesNotExist),而不仅仅是 AttributeErrors,应该的。正如Pi Delport 所指出的,这种行为实际上在Python 3.2 中根据以下票证得到了纠正:http://bugs.python.org/issue9666。此外——冒着听起来固执己见的风险——我相信上面的try/except 结构更能代表 Django 的工作方式,而使用hasattr 会给新手带来问题,这可能会造成 FUD 并传播坏习惯.

    编辑Don Kirkby's合理的妥协在我看来也是合理的。

    【讨论】:

      【解决方案3】:

      我喜欢joctee's answer,因为它很简单。

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

      其他评论者担心它可能不适用于某些版本的 Python 或 Django,但 the Django documentation 将此技术显示为选项之一:

      你也可以使用 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

      这是一个可执行的 sn-p 供您使用选项。如果您安装了 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()
      

      【讨论】:

        【解决方案4】:

        使用 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!
        

        我想你可以使用它作为一个通用函数来获取任何反向 OneToOne 实例,给定一个原始类(这里:您的配置文件类)和一个相关实例(这里:request.user)。

        【讨论】:

          【解决方案5】:

          使用select_related

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

          【讨论】:

          • 我知道它是这样工作的,但是 select_related 的这种行为是否真的记录在案?
          • 我刚刚在 Django 1.9.2 中尝试过这个,它引发了RelatedObjectDoesNotExist
          【解决方案6】:

          如果你有模型

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

          而且您只需要知道 UserProfile 存在/不存在的任何用户 - 从数据库的角度来看,最有效的方式是使用 exists 查询

          Exists 查询将仅返回布尔值,而不是像 hasattr(request.user, 'type1profile') 这样的 反向属性访问 - 这将生成 get 查询 并返回完整的对象表示

          要做到这一点 - 你需要向用户模型添加一个属性

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

          【讨论】:

            【解决方案7】:

            我正在使用 has_attr 和 is 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
            

            【讨论】:

              【解决方案8】:

              其中一种智能方法是添加自定义字段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
              猜你喜欢
              • 2023-03-02
              • 2016-12-29
              • 2020-08-22
              • 2018-10-29
              • 2014-01-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-11-16
              相关资源
              最近更新 更多