Django: Return 'None' from OneToOneField if related object doesn't exist?

后端 未结 6 1396
失恋的感觉
失恋的感觉 2021-02-04 01:48

I\'ve got a Django class like this:

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

class Egg(m.Model):
    breakfast = m.OneToOneField(Break         


        
相关标签:
6条回答
  • 2021-02-04 02:04

    This custom django field will do exactly what you want:

    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
    

    To use it:

    class Breakfast(models.Model):
        pass
        # other fields
    
    class Egg(m.Model):
        breakfast = OneToOneOrNoneField(Breakfast, related_name="egg")
    
    breakfast = Breakfast()
    assert breakfast.egg == None
    
    0 讨论(0)
  • 2021-02-04 02:04

    Django 1.10 solution as by Fedor at accepted answer:

    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
    
    0 讨论(0)
  • 2021-02-04 02:13

    OmerGertel did already point out the null option. However, if I understand your logical model right, then what you actually need is a unique and nullable foreign key from Breakfast to Egg. So a breakfast may or may not have an egg, and a particular egg can only be associated with one breakfast.

    I used this model:

    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]
    

    and this admin definition:

    class EggAdmin(admin.ModelAdmin):
        pass
    
    class BreakfastAdmin(admin.ModelAdmin):
        pass
    
    admin.site.register(Egg, EggAdmin)
    admin.site.register(Breakfast, BreakfastAdmin)
    

    Then I could create and assign an egg in the edit page for a breakfast, or just do not assign one. In the latter case, the egg property of the breakfast was None. A particular egg already assigned to some breakfast could not be selected for another one.

    EDIT:

    As OmerGertel already said in his comment, you could alternatively write this:

        egg = models.OneToOneField(Egg, null=True, blank=True)
    
    0 讨论(0)
  • 2021-02-04 02:24

    I know that on ForeignKey you can have null=True when you want to allow the model not to point to any other model. OneToOne is only a special case of a 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 讨论(0)
  • 2021-02-04 02:24

    I would recommend using try / except Egg.DoesNotExist whenever you need to access Breakfast.egg; doing so makes it very clear what's going on for people reading your code, and this is the canonical way of handling nonexistent records in Django.

    If you really want to avoid cluttering your code with try / excepts, you could define a get_egg method on Breakfast like so:

    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
    

    This will make it clearer to people reading your code that eggs are derived, and it may hint at the fact that a lookup gets performed when one calls Breakfast.get_egg().

    Personally, I'd go for the former approach in order to keep things as clear as possible, but I could see why one may be inclined to use the latter approach instead.

    0 讨论(0)
  • 2021-02-04 02:28

    I just ran into this problem, and found an odd solution to it: if you select_related(), then the attribute will be None if no related row exists, instead of raising an 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
    

    I have no idea if this can be considered a stable feature though.

    0 讨论(0)
提交回复
热议问题