检查Django中OneToOneField是否为None
我有两个模型,像这样:
class Type1Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
class Type2Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
如果用户有Type1或Type2的个人资料,我需要执行一些操作:
if request.user.type1profile != None:
# do something
elif request.user.type2profile != None:
# do something else
else:
# do something else
但是,对于那些没有Type1或Type2个人资料的用户,像这样执行代码会出现以下错误:
Type1Profile matching query does not exist.
我该如何检查用户拥有的个人资料类型呢?
谢谢
8 个回答
我喜欢joctee的回答,因为它非常简单。
if hasattr(request.user, 'type1profile'):
# do something
elif hasattr(request.user, 'type2profile'):
# do something else
else:
# do something else
其他评论者提到,这种方法可能在某些版本的Python或Django上不太适用,但Django的官方文档把这个技巧列为可选方案之一:
你也可以使用hasattr来避免需要捕获异常:
>>> hasattr(p2, 'restaurant')
False
当然,文档中也展示了捕获异常的技巧:
p2没有关联的餐厅:
>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>> p2.restaurant
>>> except ObjectDoesNotExist:
>>> print("There is no restaurant here.")
There is no restaurant here.
我同意Joshua的看法,捕获异常能让事情变得更清楚,但我觉得这样看起来有点乱。也许这是一种合理的折中方案?
>>> print(Restaurant.objects.filter(place=p2).first())
None
这只是通过地点查询Restaurant
对象。如果那个地方没有餐厅,它会返回None
。
这里有一段可执行的代码供你尝试不同的选项。如果你安装了Python、Django和SQLite3,它应该可以直接运行。我在Python 3.8.10和Django 4.0.2上测试过。
""" Django models in a single, runnable file.
Based on Nsukami's blog post: https://nskm.xyz/posts/dsfp/
To get it running, copy it into a directory named udjango:
$ pip install django
$ python udjango_models.py
Tested with Django 4.0 and Python 3.8.
"""
import logging
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'
DB_FILE = NAME + '.db'
def main():
setup()
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return "%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place, on_delete=models.CASCADE, primary_key=True)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
def __str__(self):
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():
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()
要检查一个可为空的一对一关系在某个模型中是否为null,我们可以直接查看模型中对应的字段是否为None
,但这只能在一对一关系的源模型上进行测试。比如,考虑以下这两个类……
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()
……要判断一个Restaurant
是否有一个Place
,我们可以使用以下代码:
>>> 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!
要判断一个Place
是否有一个Restaurant
,需要明白的是,如果没有对应的餐厅,访问Place
实例上的restaurant
属性会抛出一个Restaurant.DoesNotExist
的异常。这是因为Django在内部使用QuerySet.get()
进行查找。例如:
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
>>> p2.restaurant
Traceback (most recent call last):
...
DoesNotExist: Restaurant matching query does not exist.
在这种情况下,简单明了的方法是使用标准的try
/ except
结构来判断一个Place
是否有一个Restaurant
,具体可以参考这里。
>>> try:
>>> restaurant = p2.restaurant
>>> except Restaurant.DoesNotExist:
>>> print "Place has no restaurant!"
>>> else:
>>> # Do something with p2's restaurant here.
虽然joctee建议使用hasattr
在实际操作中是可行的,但这实际上只是偶然有效,因为hasattr
会抑制所有异常(包括DoesNotExist
),而不仅仅是AttributeError
,这并不是它应该有的行为。正如Pi Delport所指出的,这种行为在Python 3.2中已经被修正,具体可以查看以下链接:http://bugs.python.org/issue9666。此外——尽管可能听起来有些主观——我认为上面的try
/ except
结构更能代表Django的工作方式,而使用hasattr
可能会让新手感到困惑,从而产生误解并养成不好的习惯。
编辑 Don Kirkby的合理折中方案我也觉得不错。
要检查一个一对一的关系是否存在,你可以使用 hasattr
这个函数:
if hasattr(request.user, 'type1profile'):
# do something
elif hasattr(request.user, 'type2profile'):
# do something else
else:
# do something else