python - many - Django: ¿Devuelve ''Ninguno'' desde OneToOneField si el objeto relacionado no existe?
onetoonefield django (6)
Tengo una clase de Django como esta:
class Breakfast(m.Model):
# egg = m.OneToOneField(Egg)
...
class Egg(m.Model):
breakfast = m.OneToOneField(Breakfast, related_name="egg")
¿Es posible breakfast.egg == None
si no hay un Egg
relacionado con el Breakfast
?
Editar : Olvidé mencionar: prefiero no cambiar el related_name
a algo como related_name="_egg"
, luego tengo algo como:
@property
def egg(self):
try:
return self.egg
except ...:
return None
Porque uso el nombre de egg
en las consultas, y prefiero no tener que cambiar las consultas para usar _egg
.
Acabo de encontrarme con este problema y encontré una solución extraña para él: si selecciona_related (), entonces el atributo será None si no existe una fila relacionada, en lugar de generar un error.
>>> 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
No tengo idea si esto puede considerarse una característica estable.
Django 1.10 solución como por Fedor en la respuesta aceptada:
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
Este campo de django personalizado hará exactamente lo que quieras:
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
Para usarlo:
class Breakfast(models.Model):
pass
# other fields
class Egg(m.Model):
breakfast = OneToOneOrNoneField(Breakfast, related_name="egg")
breakfast = Breakfast()
assert breakfast.egg == None
OmerGertel ya señaló la opción null
. Sin embargo, si entiendo tu modelo lógico correcto, entonces lo que realmente necesitas es una clave externa única y con nomenclatura de Breakfast to Egg. Entonces, un desayuno puede o no tener un huevo, y un huevo en particular solo puede asociarse con un desayuno.
Usé este modelo:
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]
y esta definición de administrador:
class EggAdmin(admin.ModelAdmin):
pass
class BreakfastAdmin(admin.ModelAdmin):
pass
admin.site.register(Egg, EggAdmin)
admin.site.register(Breakfast, BreakfastAdmin)
Entonces podría crear y asignar un huevo en la página de edición para un desayuno, o simplemente no asignar uno. En este último caso, la propiedad del huevo del desayuno fue Ninguna. Un huevo particular ya asignado a un desayuno no pudo ser seleccionado para otro.
EDITAR:
Como OmerGertel ya dijo en su comentario, alternativamente podrías escribir esto:
egg = models.OneToOneField(Egg, null=True, blank=True)
Sé que en ForeignKey puede tener null=True
cuando desea permitir que el modelo no apunte a ningún otro modelo. OneToOne es solo un caso especial de 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
Yo recomendaría usar try
/ except Egg.DoesNotExist
cada vez que necesite acceder a Breakfast.egg
; al hacerlo, deja muy claro lo que está sucediendo para las personas que leen su código, y esta es la manera canónica de manejar registros inexistentes en Django.
Si realmente quiere evitar saturar su código con try
/ except
s, podría definir un método get_egg
en Breakfast
así:
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
Esto hará que sea más claro para las personas que leen su código que se derivan los huevos, y puede insinuar el hecho de que se realiza una búsqueda cuando se llama a Breakfast.get_egg()
.
Personalmente, optaría por el enfoque anterior para mantener las cosas lo más claras posible, pero pude ver por qué uno puede inclinarse a usar el último enfoque en su lugar.