I have two models like this:
class Type1Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
class Type2Profile(models.Model):
To check if the (OneToOne) relation exists or not, you can use the hasattr
function:
if hasattr(request.user, 'type1profile'):
# do something
elif hasattr(request.user, 'type2profile'):
# do something else
else:
# do something else
I like joctee's answer, because it's so simple.
if hasattr(request.user, 'type1profile'):
# do something
elif hasattr(request.user, 'type2profile'):
# do something else
else:
# 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.2
import sys
import django
from django.apps import apps
from django.apps.config import AppConfig
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, models, DEFAULT_DB_ALIAS
from django.db.models.base import ModelBase
NAME = '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_new
def 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()
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).
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
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()