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
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 simplement en testant le champ correspondant sur le modèle pour None
ness, 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 AttributeError
s, 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.
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()
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).
Utilisation select_related
!
>>> user = User.objects.select_related('type1profile').get(pk=111)
>>> user.type1profile
None
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