93

我有两个这样的模型:

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.

如何检查用户的个人资料类型?

谢谢

4

8 回答 8

102

要检查 (OneToOne) 关系是否存在,可以使用以下hasattr函数:

if hasattr(request.user, 'type1profile'):
    # do something
elif hasattr(request.user, 'type2profile'):
    # do something else
else:
    # do something else
于 2012-01-26T09:50:22.957 回答
50

只需通过测试模型上的相应字段是否正确,就可以查看特定模型的可为空的一对一关系是否为空None,但前提您必须在产生一对一关系的模型上进行测试。例如,给定这两个类……</p>

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()

...要查看 aRestaurant是否有 a 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!

要查看 aPlace是否有 a Restaurant,重要的是要了解,如果没有相应的餐厅,则restaurant在 的实例上引用属性Place会引发异常。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.

在这种情况下,奥卡姆剃刀占上风,确定 a 是否Place具有 a的最佳方法将是此处Restautrant所述的标准try/except构造。

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

虽然 joctee 建议hasattr在实践中使用有效,但它实际上只是偶然起作用,因为它会hasattr抑制所有异常(包括DoesNotExist)而不是AttributeErrors,就像它应该的那样。正如Pi Delport指出的那样,这种行为实际上在 Python 3.2 中已根据以下票证得到纠正:http: //bugs.python.org/issue9666。此外——冒着听起来固执己见的风险——我相信上面的try/except结构更能代表 Django 的工作方式,而使用hasattr它会给新手带来问题,这可能会造成 FUD 并传播坏习惯。

编辑 Don Kirkby 的合理妥协对我来说似乎也是合理的。

于 2014-03-07T01:59:24.420 回答
22

我喜欢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()
于 2015-09-23T18:31:40.110 回答
10

使用 try/except 块怎么样?

def get_profile_or_none(user, profile_cls):

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

    return profile

然后,像这样使用!

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!

我想你可以使用它作为一个通用函数来获取任何反向 OneToOne 实例,给定一个原始类(这里:您的配置文件类)和一个相关实例(这里:request.user)。

于 2011-02-04T18:44:10.653 回答
3

使用select_related

>>> user = User.objects.select_related('type1profile').get(pk=111)
>>> user.type1profile
None
于 2013-02-07T04:37:54.260 回答
2

如果你有模型

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

而且您只需要知道 UserProfile 存在/不存在的任何用户 -从数据库的角度来看使用存在查询的最有效方式

Exists 查询将只返回布尔值,而不是像反向属性访问那样hasattr(request.user, 'type1profile')- 这将生成get 查询并返回完整的对象表示

要做到这一点 - 您需要向 User 模型添加一个属性

class User(AbstractBaseUser)

@property
def has_profile():
    return UserProfile.objects.filter(user=self.pk).exists()
于 2019-10-30T11:10:21.623 回答
0

一种聪明的方法是添加自定义字段OneToOneOrNoneField使用它[适用于 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)

执行

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

用法

r = Restaurant(serves_hot_dogs=True, serves_pizza=False)
r.place  # will return None
于 2020-06-22T18:16:09.553 回答
0

我正在使用 has_attr 和 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
于 2019-07-14T12:35:16.420 回答