Проверьте, нет ли OneToOneField в Django

У меня есть две модели:

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

Но для пользователей, у которых нет профилей типа 1 или типа2, выполнение такого кода вызывает следующую ошибку:

Type1Profile matching query does not exist.

Как проверить тип профиля, который пользователь имеет?

Спасибо

Ответ 1

Чтобы проверить, существует ли отношение (OneToOne) или нет, вы можете использовать функцию hasattr:

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

Ответ 2

Можно проверить, является ли нулевое отношение "один-к-одному" нулевым для конкретной модели просто путем тестирования соответствующего поля в модели для None ness, но только если вы протестируете на модели, где один-на-один возникает одно отношение. Например, учитывая эти два класса...

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, важно понять, что ссылка на свойство 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.

В этом случае преобладает бритва Occam, и лучший способ сделать определение о том, имеет ли Place Restautrant, стандартную конструкцию try/except, как описано .

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

Хотя использование joctee для использования hasattr работает на практике, оно действительно работает только случайно, поскольку hasattr подавляет все исключения (включая DoesNotExist), а не только AttributeError s, как и должно было. Как отметил Piet Delport, это поведение было фактически исправлено в Python 3.2 за следующий билет: http://bugs.python.org/issue9666. Более того - и с риском озвучивания - я считаю, что приведенная выше конструкция try/except более характерна для работы Django, а использование hasattr может помешать проблеме для новичков, что может создать FUD и распространять вредные привычки.

Ответ 3

Мне нравится 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.

Я согласен с Джошуа в том, что, поймав это исключение, проясняю, что происходит, но мне это кажется беспорядочным. Возможно, это разумный компромисс?

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

Это просто запрос объектов Restaurant по месту. Возвращает None если в этом месте нет ресторана.

Здесь исполняемый фрагмент для вас, чтобы играть с параметрами. Если у вас установлены Python, Django и SQLite3, он должен просто запуститься. Я протестировал его с Python 2.7, Python 3.4, Django 1.9.2 и 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()

Ответ 4

Как насчет использования 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).

Ответ 5

Используйте select_related!

>>> user = User.objects.select_related('type1profile').get(pk=111)
>>> user.type1profile
None

Ответ 6

Я использую комбинацию has_attr и 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