Подтвердить что ты не робот

Django: возвращает "None" из OneToOneField, если связанный объект не существует?

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

4b9b3361

Ответ 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

Ответ 2

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

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

Ответ 3

Я знаю, что на 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

Ответ 4

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. Отдельное яйцо, уже назначенное на завтрак, не может быть выбрано для другого.

EDIT:

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

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

Ответ 5

Решение 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

Ответ 6

Я бы рекомендовал использовать try/except Egg.DoesNotExist всякий раз, когда вам нужно получить доступ к Breakfast.egg; это делает очень понятным, что происходит для людей, читающих ваш код, и это canonical way обработки несуществующих записей в 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().

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