Check if OneToOneField is None in Django

后端 未结 8 1627
走了就别回头了
走了就别回头了 2021-01-30 10:04

I have two models like this:

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


class Type2Profile(models.Model):
            


        
相关标签:
8条回答
  • 2021-01-30 10:29

    To check if the (OneToOne) relation exists or not, you can use the hasattr function:

    if hasattr(request.user, 'type1profile'):
        # do something
    elif hasattr(request.user, 'type2profile'):
        # do something else
    else:
        # do something else
    
    0 讨论(0)
  • 2021-01-30 10:31

    I like joctee's answer, because it's so simple.

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

    Other commenters have raised concerns that it may not work with certain versions of Python or Django, but the Django documentation shows this technique as one of the options:

    You can also use hasattr to avoid the need for exception catching:

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

    Of course, the documentation also shows the exception catching technique:

    p2 doesn’t have an associated restaurant:

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

    I agree with Joshua that catching the exception makes it clearer what's happening, but it just seems messier to me. Perhaps this is a reasonable compromise?

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

    This is just querying the Restaurant objects by place. It returns None if that place has no restaurant.

    Here's an executable snippet for you to play with the options. If you have Python, Django, and SQLite3 installed, it should just run. I tested it with Python 2.7, Python 3.4, Django 1.9.2, and 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()
    
    0 讨论(0)
  • 2021-01-30 10:41

    How about using try/except blocks?

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

    Then, use like this!

    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!
    

    I suppose you could use this as a generic function to get any reverse OneToOne instance, given an originating class (here: your profile classes) and a related instance (here: request.user).

    0 讨论(0)
  • 2021-01-30 10:41

    I am using a combination of has_attr and 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
    
    0 讨论(0)
  • 2021-01-30 10:42

    One of the smart approaches will be to add custom field OneToOneOrNoneField and use it [works for 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)
    

    Implementation

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

    Usage

    r = Restaurant(serves_hot_dogs=True, serves_pizza=False)
    r.place  # will return None
    
    0 讨论(0)
  • 2021-01-30 10:45

    in case you have the Model

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

    And you just need to know for any User that UserProfile exists/or not - the most efficient way from the database point of view to use exists query.

    Exists query will return just boolean, rather than reverse attribute access like hasattr(request.user, 'type1profile') - which will generate get query and return full object representation

    To do it - you need to add a property to the User model

    class User(AbstractBaseUser)
    
    @property
    def has_profile():
        return UserProfile.objects.filter(user=self.pk).exists()
    
    0 讨论(0)
提交回复
热议问题