Перевірте, чи не є OneToOneField None в Django


86

У мене є дві такі моделі:

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.

Як я можу перевірити тип профілю, який має користувач?

Дякую

Відповіді:


93

Щоб перевірити, чи існує (OneToOne) відношення чи ні, можна скористатися hasattrфункцією:

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

4
Дякуємо за це рішення. На жаль, це не працює постійно. Якщо ви хочете працювати select_related()зараз чи в майбутньому - або, можливо, навіть щоб бути впевненим, що ви також маєте справу з іншими видами магії, які можуть трапитися деінде - вам доведеться продовжити тест наступним чином:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
укладач класів

7
Зверніть увагу, що в Python <3.2, hasattrбуде проковтнуто всі винятки, які трапляються під час пошуку бази даних, а не тільки DoesNotExist. Це, мабуть, зламано, і не те, що ви хочете.
Пі Дельпорт,

не працює з python 2.7. Навіть якщо OneToOne не існує, він повертає об'єкт django.db.models.fields.related.RelatedManager.
алекспірин

@alartur, яку версію django ви використовуєте?
joctee

Django 1.5. Але я вирішив своє особливе питання, здійснивши те, що я хотів зробити, зовсім по-іншому.
алекспірин

48

Можна перевірити, чи є нульове відношення "один до одного" нульовим для конкретної моделі, просто протестувавши відповідне поле на моделі на наявність 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має 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!

Щоб побачити, чи Placeмає 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.

У цьому сценарії превалює бритва Оккама, і найкращим підходом для визначення того, чи 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.

Незважаючи на те, що пропозиція жокета використовувати hasattrтвори на практиці, вона насправді працює лише випадково, оскільки hasattrпригнічує всі винятки (включаючи DoesNotExist) на відміну від просто AttributeErrors, як і слід. Як зазначив Пі Дельпорт , ця поведінка була фактично виправлена ​​в Python 3.2 згідно з таким тикетом: http://bugs.python.org/issue9666 . Крім того - і в Ризикуючи впертий - Я вважаю , що вище try/ exceptконструкція є більш представницьким , як працює Django, при використанні hasattrможе затьмарити проблеми для новачків, які можуть створити FUD і поширення шкідливих звичок.

EDIT Дон Кіркбі в розумний компроміс і , здається розумним мені.


19

Мені подобається відповідь учасника , тому що це так просто.

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

10

Як щодо використання спроб / крім блоків?

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


3

Використовуйте select_related!

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

2
Я знаю, що це працює так, але чи справді така поведінка select_related задокументована?
Кос

3
Я щойно спробував це в Django 1.9.2, і це підвищує RelatedObjectDoesNotExist.
Дон Кіркбі,

1

на випадок, якщо у вас є Модель

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

І вам просто потрібно знати для будь-якого Користувача, що UserProfile існує / ні - найефективніший спосіб з точки зору бази даних використовувати запит про існування .

Існуючий запит повертає лише логічне значення, а не зворотний доступ до атрибутів, наприклад hasattr(request.user, 'type1profile')- що генерує запит отримання та повертає повне представлення об’єкта

Для цього - вам потрібно додати властивість до моделі користувача

class User(AbstractBaseUser)

@property
def has_profile():
    return UserProfile.objects.filter(user=self.pk).exists()

0

Я використовую комбінацію 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

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

для django 1.8 вам потрібно використовувати SingleRelatedObjectDescriptorзамість ReverseOneToOneDescriptorцього from django.db.models.fields.related import SingleRelatedObjectDescriptor
pymen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.