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
.
Ответы
Ответ 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()
.
Лично я бы пошел на прежний подход, чтобы сделать все как можно яснее, но я мог понять, почему можно склоняться к использованию последнего подхода.