Django:如果相关对象不存在,从OneToOneField返回'None'?

时间:2010-10-17 20:15:54

标签: python django django-models

我有一个像这样的Django课程:

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

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

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

修改:忘记提及:我不想将related_name更改为related_name="_egg"之类的内容,而是有类似的内容:

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

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

6 个答案:

答案 0 :(得分:9)

我刚遇到这个问题,并找到了一个奇怪的解决方案:如果你select_related(),那么如果不存在相关行,该属性将为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

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

答案 1 :(得分:8)

此自定义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

答案 2 :(得分:6)

我知道在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

答案 3 :(得分:2)

Django 1.10解决方案由Fedor接受答案:

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

答案 4 :(得分:1)

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

我用过这个模型:

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)

然后我可以在编辑页面中创建并分配一个鸡蛋作为早餐,或者只是不分配一个鸡蛋。在后一种情况下,早餐的蛋属性是无。已经分配给某些早餐的特定鸡蛋不能被选择用于另一个。

修改

正如OmerGertel在评论中已经说过的那样,你可以写下这个:

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

答案 5 :(得分:0)

我建议您在需要访问try时使用except Egg.DoesNotExist / Breakfast.egg;这样做非常清楚读取代码的人会发生什么,这是处理Django中不存在的记录的canonical way

如果您真的希望避免使用try / except来混淆代码,那么可以get_egg上定义Breakfast方法这样:

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()时执行查找。

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