【问题标题】:Django: Return 'None' from OneToOneField if related object doesn't exist?Django:如果相关对象不存在,则从 OneToOneField 返回“None”?
【发布时间】:2010-10-17 20:15:54
【问题描述】:

我有一个这样的 Django 类:

class Breakfast(m.Model):
    # egg = m.OneToOneField(Egg)
    ...

class Egg(m.Model):
    breakfast = m.OneToOneField(Breakfast, related_name="egg")

如果没有与Breakfast 相关的Egg,是否可以拥有breakfast.egg == None

编辑:忘了说:我不想把related_name改成related_name="_egg"这样的东西,然后有这样的东西:

@property
def egg(self):
    try:
        return self.egg
    except ...:
        return None

因为我在查询中使用名称 egg,我宁愿不必将查询更改为使用 _egg

【问题讨论】:

    标签: python django django-models


    【解决方案1】:

    这个自定义 django 字段将完全满足您的需求:

    class SingleRelatedObjectDescriptorReturnsNone(SingleRelatedObjectDescriptor):
        def __get__(self, *args, **kwargs):
            try:
                return super(SingleRelatedObjectDescriptorReturnsNone, self).__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
    

    使用它:

    class Breakfast(models.Model):
        pass
        # other fields
    
    class Egg(m.Model):
        breakfast = OneToOneOrNoneField(Breakfast, related_name="egg")
    
    breakfast = Breakfast()
    assert breakfast.egg == None
    

    【讨论】:

    • 太棒了!顺便说一句,1.10 界面中的变化很小。 SingleRelatedObjectDescriptor 现在是 ReverseOneToOneDescriptor,__get__ 参数是 (self, instance, cls=None)
    【解决方案2】:

    Fedor 接受的 Django 1.10 解决方案:

    from django.core.exceptions import ObjectDoesNotExist
    from django.db.models.fields.related import OneToOneField
    from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor
    
    class ReverseOneToOneOrNoneDescriptor(ReverseOneToOneDescriptor):
        def __get__(self, instance, cls=None):
            try:
                return super(ReverseOneToOneOrNoneDescriptor, self).__get__(instance=instance, cls=cls)
            except ObjectDoesNotExist:
                return None
    
    class OneToOneOrNoneField(models.OneToOneField):
        """A OneToOneField that returns None if the related object doesn't exist"""
        related_accessor_class = ReverseOneToOneOrNoneDescriptor
    

    【讨论】:

      【解决方案3】:

      OmerGertel 确实已经指出了null 选项。但是,如果我正确理解您的逻辑模型,那么您真正需要的是从早餐到鸡蛋的 唯一且可为空的外键。因此,一份早餐可能有也可能没有鸡蛋,而一个特定的鸡蛋只能与一份早餐相关联。

      我用过这个模型:

      class Egg(models.Model):
          quality = models.CharField(max_length=50)
          def __unicode__(self):
              return self.quality
      
      class Breakfast(models.Model):
          dish = models.TextField()
          egg = models.ForeignKey(Egg, unique=True, null=True, blank=True)
          def __unicode__(self):
              return self.dish[:30]
      

      还有这个管理员定义:

      class EggAdmin(admin.ModelAdmin):
          pass
      
      class BreakfastAdmin(admin.ModelAdmin):
          pass
      
      admin.site.register(Egg, EggAdmin)
      admin.site.register(Breakfast, BreakfastAdmin)
      

      然后我可以在编辑页面中创建并分配一个鸡蛋作为早餐,或者不分配一个。在后一种情况下,早餐的 egg 属性为 None。已分配给某个早餐的特定鸡蛋无法再选择另一个。

      编辑:

      正如 OmerGertel 在他的评论中所说,您也可以这样写:

          egg = models.OneToOneField(Egg, null=True, blank=True)
      

      【讨论】:

      • 这主要是可行的......但是,正如 Omer 的建议:它将egg 列放在Breakfast 表中,这(出于各种原因)在其他方面使生活变得更加复杂。不过,感谢您的回答。
      【解决方案4】:

      我知道在 ForeignKey 上,当您希望模型不指向任何其他模型时,您可以使用 null=True。 OneToOne 只是 ForeignKey 的一个特例:

      class Place(models.Model)
          address = models.CharField(max_length=80)
      class Shop(models.Model)
          place = models.OneToOneField(Place, null=True)
          name = models.CharField(max_length=50)
          website = models.URLField()
      
      >>>s1 = Shop.objects.create(name='Shop', website='shop.com')
      >>>print s1.place
      None
      

      【讨论】:

      • 这不是我的问题,不过……我希望每个Egg 都属于Breakfast,但不是每个Breakfast 都必须有一个Egg
      • 如果您将OneToOneField 移动到Breakfastnull=True,这难道不是您需要的吗?我认为在那种情况下breakfast.egg 可以是None,但如果找不到egg.breakfast 会抛出DoesNotExists
      • 不完全是:在Breakfast 上拥有OneToOne 字段会将行放在Breakfast 表上,这(由于各种原因)不是我想要的。
      【解决方案5】:

      我刚刚遇到这个问题,发现了一个奇怪的解决方案:如果你选择相关(),那么如果不存在相关行,则属性将为 None,而不是引发错误。

      >>> print Breakfast.objects.get(pk=1).egg
      Traceback (most recent call last):
      ...
      DoesNotExist: Egg matching query does not exist
      
      >>> print Breakfast.objects.select_related("egg").get(pk=1).egg
      None
      

      我不知道这是否可以被认为是一个稳定的功能。

      【讨论】:

        【解决方案6】:

        我建议您在需要访问Breakfast.egg 时使用try / except Egg.DoesNotExist;这样做可以让阅读您的代码的人清楚地知道发生了什么,这就是在 Django 中处理不存在的记录的canonical way

        如果您真的想避免使用 try / excepts 使代码混乱,您可以Breakfast 上定义一个 get_egg 方法,如下所示:

        def get_egg(self):
            """ Fetches the egg associated with this `Breakfast`.
        
            Returns `None` if no egg is found.
            """
            try:
                return self.egg
            except Egg.DoesNotExist:
                return None
        

        这将使阅读您的代码的人更清楚鸡蛋是派生的,并且它可能暗示当调用Breakfast.get_egg()时会执行查找。

        就我个人而言,我会选择前一种方法以使事情尽可能清晰,但我可以理解为什么人们可能倾向于使用后一种方法。

        【讨论】:

          猜你喜欢
          • 2020-08-22
          • 2022-01-25
          • 1970-01-01
          • 2021-12-13
          • 2012-03-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-02-15
          相关资源
          最近更新 更多