web-dev-qa-db-fra.com

Vérifiez si OneToOneField est Aucun dans Django

J'ai deux modèles comme celui-ci:

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


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

Je dois faire quelque chose si l'utilisateur a un profil Type1 ou Type2:

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

Mais, pour les utilisateurs qui n'ont pas de profils de type1 ou de type2, l'exécution de code comme celui-ci produit l'erreur suivante:

Type1Profile matching query does not exist.

Comment puis-je vérifier le type de profil d'un utilisateur?

Merci

77
John Bright

Pour vérifier si la relation (OneToOne) existe ou non, vous pouvez utiliser la fonction hasattr:

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

Il est possible de voir si une relation un-à-un nullable est nulle pour un modèle particulier simplement en testant le champ correspondant sur le modèle pour Noneness, mais seulement si vous testez sur le modèle d'où provient la relation biunivoque. Par exemple, étant donné ces deux classes…

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

… Pour voir si un Restaurant a un Place, nous pouvons utiliser le code suivant:

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

Pour voir si un Place a un Restaurant, il est important de comprendre que référencer la propriété restaurant sur une instance de Place soulève un Restaurant.DoesNotExist exception s'il n'y a pas de restaurant correspondant. Cela se produit car Django effectue une recherche en interne à l'aide de QuerySet.get(). Par exemple:

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

Dans ce scénario, le rasoir d'Occam prévaut et la meilleure approche pour déterminer si un Place a un Restautrant serait un standard try/except construire comme décrit ici .

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

Alors que la suggestion de joctee d'utiliser hasattr fonctionne en pratique, elle ne fonctionne vraiment que par accident puisque hasattr supprime toutes les exceptions ( y compris DoesNotExist) par opposition à seulement AttributeErrors, comme il se doit. Comme l'a souligné Piet Delport, ce comportement a en fait été corrigé dans Python 3.2 par le ticket suivant: http://bugs.python.org/issue9666 . En outre - et au risque d'avoir l'air d'opinion - je pense que la construction try/except ci-dessus est plus représentative du fonctionnement de Django, tout en utilisant hasattr peut assombrir le problème pour les débutants, ce qui peut créer du FUD et propager de mauvaises habitudes.

42
Joshua Pokotilow

J'aime réponse de joctee , parce que c'est si simple.

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

D'autres commentateurs se sont inquiétés du fait que cela ne fonctionnerait pas avec certaines versions de Python ou Django, mais la Django montre cette technique) comme l'une des options:

Vous pouvez également utiliser hasattr pour éviter d'avoir à intercepter des exceptions:

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

Bien sûr, la documentation montre également la technique de capture d'exception:

p2 n'a pas de restaurant associé:

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

Je suis d'accord avec Joshua que la capture de l'exception rend plus clair ce qui se passe, mais cela me semble plus compliqué. C'est peut-être un compromis raisonnable?

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

Il s'agit simplement d'interroger les objets Restaurant par emplacement. Il renvoie None si cet endroit n'a pas de restaurant.

Voici un extrait exécutable pour que vous puissiez jouer avec les options. Si vous avez installé Python, Django et SQLite3, il devrait simplement fonctionner. Je l'ai testé avec Python 2.7, Python 3.4, Django 1.9.2 et 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()
14
Don Kirkby

Que diriez-vous d'utiliser des blocs 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

Ensuite, utilisez comme ça!

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!

Je suppose que vous pouvez utiliser cela comme une fonction générique pour obtenir n'importe quelle instance OneToOne inverse, étant donné une classe d'origine (ici: vos classes de profil) et une instance associée (ici: request.user).

9
Geradeausanwalt

Utilisation select_related!

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

J'utilise une combinaison de has_attr et est 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
FreeWorlder