Django: Вернуть None из OneToOneField, если связанный объект не существует? - PullRequest
21 голосов
/ 18 октября 2010

У меня есть класс 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 ]

9 голосов
/ 20 августа 2015

Это пользовательское поле 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
9 голосов
/ 18 января 2013

Я только что столкнулся с этой проблемой и нашел странное решение: если вы выберете 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

Я понятия не имею, можно ли считать это стабильной функцией.

6 голосов
/ 18 октября 2010

Я знаю, что в 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
2 голосов
/ 05 апреля 2017

Решение 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
1 голос
/ 18 октября 2010

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)

Тогда я мог бы создать и назначить яйцо на странице редактирования для завтрака или просто не назначить его.В последнем случае свойство яйца на завтраке было None.Определенное яйцо, уже назначенное на один завтрак, не может быть выбрано для другого.

РЕДАКТИРОВАТЬ:

Как уже сказал OmerGertel в своем комментарии, вы могли бы альтернативно написать это:

    egg = models.OneToOneField(Egg, null=True, blank=True)
0 голосов
/ 07 марта 2014

Я бы рекомендовал использовать try / except Egg.DoesNotExist всякий раз, когда вам нужен доступ к Breakfast.egg;это очень ясно дает понять, что происходит с людьми, читающими ваш код, и это канонический способ обработки несуществующих записей в Django.

Если вы действительно хотите избежать загромождения вашего кодаtry / except s, вы могли бы определить 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().

Лично я бы пошел на первый подход, чтобы все было как можно яснее, ноЯ мог понять, почему можно использовать последний подход вместо этого.

...