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 du code comme celui-ci génère l’erreur suivante:

 Type1Profile matching query does not exist. 

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

Merci

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 

Il est possible de voir si une relation un-à-un nullable est nulle pour un modèle particulier en testant simplement le champ correspondant sur le modèle pour None , mais seulement si vous testez sur le modèle d’où provient la relation un-à-un. Par exemple, compte tenu de 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 une 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 dispose d’un Restaurant , il est important de comprendre que le référencement de la propriété restaurant sur une instance de Place génère une exception Restaurant.DoesNotExist s’il n’y a pas de restaurant correspondant. Cela se produit parce que Django effectue une recherche en interne en utilisant 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 razor d’Occam prévaut, et la meilleure approche pour déterminer si un Place possède ou non un Restautrant serait une construction try / except standard telle que décrite ici .

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

Bien que la suggestion de hasattr utiliser hasattr fonctionne dans la pratique, cela ne fonctionne vraiment que par hasard, car hasattr supprime toutes les exceptions (y compris DoesNotExist ), contrairement à ce qui devrait l’être. Comme Piet Delport l’a souligné, ce comportement a été corrigé dans Python 3.2 par le ticket suivant: http://bugs.python.org/issue9666 . En outre – et au risque de paraître critique – je pense que la construction try / except ci-dessus est plus représentative du fonctionnement de Django, alors que hasattr peut hasattr le problème des débutants, ce qui peut créer des FUD et répandre de mauvaises habitudes.

J’aime la 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 craignent de 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 suivantes:

Vous pouvez également utiliser hasattr pour éviter d’avoir à capturer 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 sur le fait que rattraper l’exception rend plus clair ce qui se passe, mais cela me semble tout simplement plus compliqué. C’est peut-être un compromis raisonnable?

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

Cela ne fait qu’interroger les objects du Restaurant par endroit. 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 Python, Django et SQLite3 sont installés, il devrait simplement être lancé. 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 atsortingbute.')) 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() 

Pourquoi ne pas utiliser les 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 pourriez l’utiliser comme une fonction générique pour obtenir une instance OneToOne inversée, en fonction d’une classe d’origine (ici: vos classes de profil) et d’une instance associée (ici: request.user).

Utilisez select_related !

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