94 votes

Vérifier si OneToOneField est None dans Django

Je possède deux modèles de ce type :

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

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

J'ai besoin de faire quelque chose si l'utilisateur a le 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 profil de type 1 ou de type 2, l'exécution d'un tel code produit l'erreur suivante :

Type1Profile matching query does not exist.

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

Merci

104voto

joctee Points 674

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

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

4 votes

Merci pour cette solution. Malheureusement, elle ne fonctionne pas toujours. Dans le cas où vous voulez travailler avec select_related() maintenant ou à l'avenir -- ou peut-être même pour être sûr de gérer d'autres types de magie qui peuvent se produire ailleurs -- vous devez étendre le test comme suit : if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None

7 votes

Notez que dans Python < 3.2, hasattr avalera tous les exceptions qui se produisent pendant la consultation de la base de données, et pas seulement DoesNotExist . C'est probablement cassé, et ce n'est pas ce que vous voulez.

0 votes

Ne fonctionne pas avec python 2.7. Même si OneToOne n'existe pas, il retourne un objet django.db.models.fields.related.RelatedManager.

50voto

Joshua Pokotilow Points 403

Il est possible de voir si une relation biunivoque annulable est nulle pour un modèle particulier en testant simplement le champ correspondant sur le modèle pour None ness, mais uniquement 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 le fait de faire référence à l'option restaurant sur une instance de Place soulève un Restaurant.DoesNotExist exception s'il n'y a pas de restaurant correspondant. Cela se produit parce que Django effectue une recherche en interne en utilisant la commande 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 l'emporte, et la meilleure approche pour déterminer si oui ou non une Place a un Restautrant serait une norme try / except construire selon la description 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 les hasattr fonctionne dans la pratique, elle ne fonctionne vraiment que par accident puisque hasattr supprime tous les exceptions (notamment DoesNotExist ) au lieu de simplement AttributeError comme il se doit. Comme Pi Delport a souligné, ce comportement a été corrigé dans Python 3.2 par le billet suivant : http://bugs.python.org/issue9666 . En outre, et au risque de paraître partial, je pense que ce qui précède try / except est plus représentative du fonctionnement de Django, tandis que l'utilisation de la méthode hasattr peut brouiller les pistes pour les nouveaux venus, ce qui peut créer des doutes et répandre de mauvaises habitudes.

EDIT Don Kirkby's Un compromis raisonnable me semble également raisonnable.

22voto

Don Kirkby Points 12671

J'aime La réponse de joctee parce que c'est tellement 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 qu'il pourrait ne pas fonctionner avec certaines versions de Python ou de Django, mais la documentation de Django présente cette technique comme l'une des options :

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

hasattr(p2, 'restaurant') False

Bien entendu, la documentation montre également la technique de capture des exceptions :

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 le fait d'attraper l'exception rende plus clair ce qui se passe, mais ça me semble plus désordonné. Peut-être est-ce un compromis raisonnable ?

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

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

Voici un extrait d'exécutable pour vous permettre de jouer avec les options. Si vous avez Python, Django, et SQLite3 installés, il devrait simplement s'exécuter. 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()

10voto

Geradeausanwalt Points 765

Pourquoi ne pas 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 ceci !

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 pourriez l'utiliser comme une fonction générique pour obtenir n'importe quelle instance OneToOne inversée, étant donné une classe d'origine (ici : vos classes de profil) et une instance liée (ici : request.user).

3voto

ivan133 Points 98

Utilisez select_related !

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

2 votes

Je sais que cela fonctionne ainsi, mais ce comportement de select_related est-il réellement documenté ?

3 votes

Je viens juste d'essayer ceci dans Django 1.9.2, et cela pose le problème suivant RelatedObjectDoesNotExist .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X