Kiểm tra xem OneToOneField có phải là Không trong Django không


86

Tôi có hai mô hình như thế này:

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


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

Tôi cần phải làm gì đó nếu người dùng có cấu hình Loại1 hoặc Loại2:

if request.user.type1profile != None:
    # do something
elif request.user.type2profile != None:
    # do something else
else:
    # do something else

Tuy nhiên, đối với người dùng không có cấu hình type1 hoặc type2, việc thực thi mã như vậy sẽ tạo ra lỗi sau:

Type1Profile matching query does not exist.

Làm cách nào để kiểm tra loại hồ sơ mà người dùng có?

Cảm ơn

Câu trả lời:


93

Để kiểm tra xem quan hệ (OneToOne) có tồn tại hay không, bạn có thể sử dụng hasattrhàm:

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

4
Cảm ơn bạn cho giải pháp này. Thật không may, điều này không hoạt động mọi lúc. Trong trường hợp bạn muốn làm việc với select_related()ngay bây giờ hoặc trong tương lai - hay thậm chí có thể để chắc chắn bạn cũng xử lý các loại khác của ma thuật có thể xảy ra ở những nơi khác - bạn cần phải mở rộng các thử nghiệm như sau:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
lớp stacker

7
Lưu ý rằng trong Python <3.2, hasattrsẽ nuốt tất cả các ngoại lệ xảy ra trong quá trình tra cứu cơ sở dữ liệu, và không chỉ DoesNotExist. Điều này có thể bị hỏng, và không phải những gì bạn muốn.
Pi Delport

không hoạt động với python 2.7. Ngay cả khi OneToOne không tồn tại, nó sẽ trả về một đối tượng django.db.models.fields.osystem.RelatedManager.
alexpirine

@alartur Bạn đang sử dụng phiên bản django nào?
joctee

Django 1.5. Nhưng tôi đã giải quyết vấn đề cụ thể của mình bằng cách thực hiện những gì tôi muốn làm theo một cách hoàn toàn khác.
alexpirine

48

Có thể xem liệu mối quan hệ một-một có thể null có phải là null cho một mô hình cụ thể hay không chỉ bằng cách kiểm tra trường tương ứng trên mô hình cho Noneness, nhưng chỉ khi bạn kiểm tra trên mô hình mà mối quan hệ một-một bắt nguồn. Ví dụ, với hai lớp này…

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

… Để xem liệu a Restaurantcó a không Place, chúng ta có thể sử dụng đoạn mã sau:

>>> 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!

Để xem liệu a Placecó a hay không Restaurant, điều quan trọng là phải hiểu rằng việc tham chiếu thuộc restauranttính trên một trường hợp của Placemột Restaurant.DoesNotExistngoại lệ nếu không có nhà hàng tương ứng. Điều này xảy ra vì Django thực hiện tra cứu nội bộ bằng cách sử dụng QuerySet.get(). Ví dụ:

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

Trong kịch bản này, chiếm ưu thế dao cạo Occam, và phương pháp tốt nhất để thực hiện một quyết tâm về việc có hay không một Placecó một Restautrantsẽ là một tiêu chuẩn try/ exceptxây dựng như mô tả ở đây .

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

Mặc dù gợi ý của joctee để sử dụng hasattrhoạt động trong thực tế, nhưng nó thực sự chỉ hoạt động một cách tình cờ vì hasattrngăn chặn tất cả các ngoại lệ (bao gồm DoesNotExist) trái ngược với chỉ AttributeErrors, giống như nó nên. Như Pi Delport đã chỉ ra, hành vi này đã thực sự được sửa chữa trong Python 3.2 theo vé sau: http://bugs.python.org/issue9666 . Hơn nữa - và có nguy cơ nghe có vẻ cố chấp - tôi tin rằng cấu trúc try/ exceptcấu trúc trên đại diện hơn cho cách hoạt động của Django, trong khi việc sử dụng hasattrcó thể làm mờ vấn đề đối với người mới, điều này có thể tạo ra FUD và lây lan thói quen xấu.

CHỈNH SỬA Thỏa hiệp hợp lý của Don Kirkby cũng có vẻ hợp lý đối với tôi.


19

Tôi thích câu trả lời của joctee , vì nó rất đơn giản.

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

Những người bình luận khác đã đưa ra lo ngại rằng nó có thể không hoạt động với một số phiên bản nhất định của Python hoặc Django, nhưng tài liệu Django cho thấy kỹ thuật này là một trong những tùy chọn:

Bạn cũng có thể sử dụng hasattr để tránh yêu cầu bắt ngoại lệ:

>>> hasattr(p2, 'restaurant')
False

Tất nhiên, tài liệu cũng cho thấy kỹ thuật bắt ngoại lệ:

p2 không có nhà hàng được liên kết:

>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>>     p2.restaurant
>>> except ObjectDoesNotExist:
>>>     print("There is no restaurant here.")
There is no restaurant here.

Tôi đồng ý với Joshua rằng việc nắm bắt ngoại lệ sẽ làm rõ ràng hơn những gì đang xảy ra, nhưng nó có vẻ phức tạp hơn đối với tôi. Có lẽ đây là một thỏa hiệp hợp lý?

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

Đây chỉ là truy vấn các Restaurantđối tượng theo địa điểm. Nó trả về Nonenếu nơi đó không có nhà hàng.

Đây là đoạn mã thực thi để bạn sử dụng các tùy chọn. Nếu bạn đã cài đặt Python, Django và SQLite3, nó sẽ chạy. Tôi đã thử nghiệm nó với Python 2.7, Python 3.4, Django 1.9.2 và 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

Làm thế nào về việc sử dụng khối thử / ngoại trừ?

def get_profile_or_none(user, profile_cls):

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

    return profile

Sau đó, sử dụng như thế này!

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!

Tôi cho rằng bạn có thể sử dụng nó như một hàm chung để lấy bất kỳ phiên bản OneToOne ngược nào, được cung cấp cho một lớp ban đầu (ở đây: các lớp hồ sơ của bạn) và một cá thể liên quan (tại đây: request.user).


3

Sử dụng select_related!

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

2
Tôi biết nó hoạt động như thế này, nhưng hành vi này của select_ Related có thực sự được ghi lại không?
Kos

3
Tôi vừa thử điều này trong Django 1.9.2, và nó tăng lên RelatedObjectDoesNotExist.
Don Kirkby

1

trong trường hợp bạn có Model

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

Và bạn chỉ cần biết đối với bất kỳ Người dùng nào rằng UserProfile có tồn tại / hay không - cách hiệu quả nhất từ quan điểm cơ sở dữ liệu để sử dụng truy vấn tồn tại .

Truy vấn tồn tại sẽ chỉ trả về boolean, thay vì truy cập thuộc tính ngược như hasattr(request.user, 'type1profile')- sẽ tạo truy vấn get và trả về đại diện đối tượng đầy đủ

Để làm điều đó - bạn cần thêm thuộc tính vào Mô hình người dùng

class User(AbstractBaseUser)

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

0

Tôi đang sử dụng kết hợp has_attr và không có:

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

Một trong những cách tiếp cận thông minh sẽ là thêm trường tùy chỉnh OneToOneOrNoneFieldsử dụng nó [hoạt động cho 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)

Thực hiện

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

Sử dụng

r = Restaurant(serves_hot_dogs=True, serves_pizza=False)
r.place  # will return None

đối với django 1.8, bạn cần sử dụng SingleRelatedObjectDescriptorthay vì ReverseOneToOneDescriptornhư thế này from django.db.models.fields.related import SingleRelatedObjectDescriptor
pymen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.