Check if OneToOneField is None in Django Check if OneToOneField is None in Django django django

Check if OneToOneField is None in Django


To check if the (OneToOne) relation exists or not, you can use the hasattr function:

if hasattr(request.user, 'type1profile'):    # do somethingelif hasattr(request.user, 'type2profile'):    # do something elseelse:    # do something else


It's possible to see if a nullable one-to-one relationship is null for a particular model simply by testing the corresponding field on the model for Noneness, but only if you test on the model where the one-to-one relationship originates. For example, given these two classes…

class Place(models.Model):    name = models.CharField(max_length=50)    address = models.CharField(max_length=80)class Restaurant(models.Model):  # The class where the one-to-one originates    place = models.OneToOneField(Place, blank=True, null=True)    serves_hot_dogs = models.BooleanField()    serves_pizza = models.BooleanField()

… to see if a Restaurant has a Place, we can use the following code:

>>> r = Restaurant(serves_hot_dogs=True, serves_pizza=False)>>> r.save()>>> if r.place is None:>>>    print "Restaurant has no place!"Restaurant has no place!

To see if a Place has a Restaurant, it's important to understand that referencing the restaurant property on an instance of Place raises a Restaurant.DoesNotExist exception if there is no corresponding restaurant. This happens because Django performs a lookup internally using QuerySet.get(). For example:

>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')>>> p2.save()>>> p2.restaurantTraceback (most recent call last):    ...DoesNotExist: Restaurant matching query does not exist.

In this scenario, Occam's razor prevails, and the best approach for making a determination about whether or not a Place has a Restautrant would be a standard try / except construct as described here.

>>> try:>>>     restaurant = p2.restaurant>>> except Restaurant.DoesNotExist:>>>     print "Place has no restaurant!">>> else:>>>     # Do something with p2's restaurant here.

While joctee's suggestion to use hasattr works in practice, it really only works by accident since hasattr suppresses all exceptions (including DoesNotExist) as opposed to just AttributeErrors, like it should. As Pi Delport pointed out, this behavior was actually corrected in Python 3.2 per the following ticket: http://bugs.python.org/issue9666. Furthermore — and at the risk of sounding opinionated — I believe the above try / except construct is more representative of how Django works, while using hasattr can cloud the issue for newbies, which may create FUD and spread bad habits.

EDIT Don Kirkby's reasonable compromise also seems reasonable to me.


I like joctee's answer, because it's so simple.

if hasattr(request.user, 'type1profile'):    # do somethingelif hasattr(request.user, 'type2profile'):    # do something elseelse:    # do something else

Other commenters have raised concerns that it may not work with certain versions of Python or Django, but the Django documentation shows this technique as one of the options:

You can also use hasattr to avoid the need for exception catching:

>>> hasattr(p2, 'restaurant')False

Of course, the documentation also shows the exception catching technique:

p2 doesn’t have an associated restaurant:

>>> from django.core.exceptions import ObjectDoesNotExist>>> try:>>>     p2.restaurant>>> except ObjectDoesNotExist:>>>     print("There is no restaurant here.")There is no restaurant here.

I agree with Joshua that catching the exception makes it clearer what's happening, but it just seems messier to me. Perhaps this is a reasonable compromise?

>>> print(Restaurant.objects.filter(place=p2).first())None

This is just querying the Restaurant objects by place. It returns None if that place has no restaurant.

Here's an executable snippet for you to play with the options. If you have Python, Django, and SQLite3 installed, it should just run. I tested it with Python 2.7, Python 3.4, Django 1.9.2, and SQLite3 3.8.2.

# Tested with Django 1.9.2import sysimport djangofrom django.apps import appsfrom django.apps.config import AppConfigfrom django.conf import settingsfrom django.core.exceptions import ObjectDoesNotExistfrom django.db import connections, models, DEFAULT_DB_ALIASfrom django.db.models.base import ModelBaseNAME = 'udjango'def main():    setup()    class Place(models.Model):        name = models.CharField(max_length=50)        address = models.CharField(max_length=80)        def __str__(self):              # __unicode__ on Python 2            return "%s the place" % self.name    class Restaurant(models.Model):        place = models.OneToOneField(Place, primary_key=True)        serves_hot_dogs = models.BooleanField(default=False)        serves_pizza = models.BooleanField(default=False)        def __str__(self):              # __unicode__ on Python 2            return "%s the restaurant" % self.place.name    class Waiter(models.Model):        restaurant = models.ForeignKey(Restaurant)        name = models.CharField(max_length=50)        def __str__(self):              # __unicode__ on Python 2            return "%s the waiter at %s" % (self.name, self.restaurant)    syncdb(Place)    syncdb(Restaurant)    syncdb(Waiter)    p1 = Place(name='Demon Dogs', address='944 W. Fullerton')    p1.save()    p2 = Place(name='Ace Hardware', address='1013 N. Ashland')    p2.save()    r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)    r.save()    print(r.place)    print(p1.restaurant)    # Option 1: try/except    try:        print(p2.restaurant)    except ObjectDoesNotExist:        print("There is no restaurant here.")    # Option 2: getattr and hasattr    print(getattr(p2, 'restaurant', 'There is no restaurant attribute.'))    if hasattr(p2, 'restaurant'):        print('Restaurant found by hasattr().')    else:        print('Restaurant not found by hasattr().')    # Option 3: a query    print(Restaurant.objects.filter(place=p2).first())def setup():    DB_FILE = NAME + '.db'    with open(DB_FILE, 'w'):        pass  # wipe the database    settings.configure(        DEBUG=True,        DATABASES={            DEFAULT_DB_ALIAS: {                'ENGINE': 'django.db.backends.sqlite3',                'NAME': DB_FILE}},        LOGGING={'version': 1,                 'disable_existing_loggers': False,                 'formatters': {                    'debug': {                        'format': '%(asctime)s[%(levelname)s]'                                  '%(name)s.%(funcName)s(): %(message)s',                        'datefmt': '%Y-%m-%d %H:%M:%S'}},                 'handlers': {                    'console': {                        'level': 'DEBUG',                        'class': 'logging.StreamHandler',                        'formatter': 'debug'}},                 'root': {                    'handlers': ['console'],                    'level': 'WARN'},                 'loggers': {                    "django.db": {"level": "WARN"}}})    app_config = AppConfig(NAME, sys.modules['__main__'])    apps.populate([app_config])    django.setup()    original_new_func = ModelBase.__new__    @staticmethod    def patched_new(cls, name, bases, attrs):        if 'Meta' not in attrs:            class Meta:                app_label = NAME            attrs['Meta'] = Meta        return original_new_func(cls, name, bases, attrs)    ModelBase.__new__ = patched_newdef syncdb(model):    """ Standard syncdb expects models to be in reliable locations.    Based on https://github.com/django/django/blob/1.9.3    /django/core/management/commands/migrate.py#L285    """    connection = connections[DEFAULT_DB_ALIAS]    with connection.schema_editor() as editor:        editor.create_model(model)main()