Check if OneToOneField is None in Django

Viewed 37039

I have two models like this:

class Type1Profile(models.Model):
    user = models.OneToOneField(User, unique=True)
    ...


class Type2Profile(models.Model):
    user = models.OneToOneField(User, unique=True)
    ...

I need to do something if the user has Type1 or Type2 profile:

if request.user.type1profile != None:
    # do something
elif request.user.type2profile != None:
    # do something else
else:
    # do something else

But, for users that don't have either type1 or type2 profiles, executing code like that produces the following error:

Type1Profile matching query does not exist.

How can I check the type of profile a user has?

Thanks

8 Answers

How about using try/except blocks?

def get_profile_or_none(user, profile_cls):

    try:
        profile = getattr(user, profile_cls.__name__.lower())
    except profile_cls.DoesNotExist:
        profile = None

    return profile

Then, use like this!

u = request.user
if get_profile_or_none(u, Type1Profile) is not None:
    # do something
elif get_profile_or_none(u, Type2Profile) is not None:
    # do something else
else:
    # d'oh!

I suppose you could use this as a generic function to get any reverse OneToOne instance, given an originating class (here: your profile classes) and a related instance (here: request.user).

in case you have the Model

class UserProfile(models.Model):
    user = models.OneToOneField(User, unique=True)

And you just need to know for any User that UserProfile exists/or not - the most efficient way from the database point of view to use exists query.

Exists query will return just boolean, rather than reverse attribute access like hasattr(request.user, 'type1profile') - which will generate get query and return full object representation

To do it - you need to add a property to the User model

class User(AbstractBaseUser)

@property
def has_profile():
    return UserProfile.objects.filter(user=self.pk).exists()

I am using a combination of has_attr and is None:

class DriverLocation(models.Model):
    driver = models.OneToOneField(Driver, related_name='location', on_delete=models.CASCADE)

class Driver(models.Model):
    pass

    @property
    def has_location(self):
        return not hasattr(self, "location") or self.location is None

One of the smart approaches will be to add custom field OneToOneOrNoneField and use it [works for Django >=1.9]

from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor
from django.core.exceptions import ObjectDoesNotExist
from django.db import models


class SingleRelatedObjectDescriptorReturnsNone(ReverseOneToOneDescriptor):
    def __get__(self, *args, **kwargs):
        try:
            return super().__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

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('null', True)
        kwargs.setdefault('blank', True)
        super().__init__(*args, **kwargs)

Implementation

class Restaurant(models.Model):  # The class where the one-to-one originates
    place = OneToOneOrNoneField(Place)
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()

Usage

r = Restaurant(serves_hot_dogs=True, serves_pizza=False)
r.place  # will return None
Related