2010-10-17 19 views
21

Tengo una clase de Django como esto:Django: ¿Devuelve 'Ninguno' desde OneToOneField si el objeto relacionado no existe?

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

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

¿Es posible tener breakfast.egg == None si no hay Egg relacionado con el Breakfast?

Editar: ¿Ha olvidado mencionar: Yo prefiero no cambiar el related_name a algo así como related_name="_egg", a continuación, tener algo como:

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

porque uso el nombre egg en las consultas, y me más bien, no tiene que cambiar las consultas para usar _egg.

Respuesta

6

Esta costumbre hará exactamente lo que quiere:

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 

utilizarlo:

solución
class Breakfast(models.Model): 
    pass 
    # other fields 

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

breakfast = Breakfast() 
assert breakfast.egg == None 
+1

Awesome! Por cierto, en las interfaces 1.10 hay pocos cambios. SingleRelatedObjectDescriptor ahora es ReverseOneToOneDescriptor y los parámetros '' '__get__''' son (self, instance, cls = None) – Fedor

+0

Gracias lo arreglé para trabajar con Django 1.10 –

6

Sé que en ForeignKey puede tener null=True cuando desea permitir que el modelo no apunte a ningún otro modelo. OneToOne es sólo un caso especial de un 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 
+0

Este No es mi problema, sin embargo ... Quiero que cada "huevo" pertenezca a un "desayuno", pero no todos los "desayunos" necesariamente tienen un "huevo". –

+0

Y si mueve el 'OneToOneField' a' Breakfast' con 'null = True', ¿no le proporciona esto lo que necesita? Creo que en ese caso 'breakfast.egg' puede ser' None', pero 'egg.breakfast' arrojará' DoesNotExists' si no puede encontrarlo. – OmerGertel

+1

No exactamente: tener el campo 'OneToOne' en' Breakfast' pondrá la fila en la tabla 'Breakfast', que (por diversas razones) no es lo que quiero. –

1

OmerGertel tuviera ya señalar la opción null. Sin embargo, si entiendo bien su modelo lógico, lo que realmente necesita es una única y anulable clave externa de Breakfast to Egg. Entonces, un desayuno puede o no tener un huevo, y un huevo en particular solo puede asociarse con un desayuno.

que utilizan 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 administración:

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 una . 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.

EDIT:

Como OmerGertel ya se dijo en su comentario, usted podría alternativamente escribir esto:

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

Eso funcionaría principalmente ... Pero, como con la sugerencia de Omer: pone la columna 'huevo' en la tabla' Desayuno', que (por varias razones) hace la vida más compleja de otras maneras. Gracias por la respuesta, sin embargo. –

9

Me acabo de encontrar con este problema, y ​​se encontró una solución extraña a ella: si select_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.

+3

Esto no funciona en 1.5. – TAH

+0

Funciona en django 1.4 –

0

Recomendaría usar try/except Egg.DoesNotExist cuando necesite acceder a Breakfast.egg; al hacerlo, deja muy claro lo que está sucediendo para las personas que leen su código, y este es el canonical way del manejo de registros inexistentes en Django.

Si realmente quiere evitar que saturan su código con try/except s, que 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 a la gente que lee el código que los huevos se derivan, y puede insinuar el hecho de que se realiza una búsqueda cuando uno llama al Breakfast.get_egg().

Personalmente, preferiría 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.

campo Django
1

Django 1.10 como por Fedor en 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 
Cuestiones relacionadas