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 profil type1 ou 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
python
django
django-models
one-to-one
John Bright
la source
la source
select_related()
maintenant ou dans le futur - ou peut-être même pour être sûr de gérer également d'autres sortes de magie qui peuvent se produire ailleurs - vous devez étendre le test comme suit:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
hasattr
avalera toutes les exceptions qui se produisent lors de la recherche dans la base de données, et pas seulementDoesNotExist
. Ceci est probablement cassé, et pas ce que vous voulez.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
ness, mais seulement si vous testez sur le modèle d'où provient la relation un-à-un. 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 a
Restaurant
a unPlace
, 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 a
Place
a unRestaurant
, il est important de comprendre que le référencement de larestaurant
propriété sur une instance dePlace
lève uneRestaurant.DoesNotExist
exception s'il n'y a pas de restaurant correspondant. Cela se produit car Django effectue une recherche en interne en utilisantQuerySet.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 ou nonRestautrant
serait un standardtry
/ uneexcept
construction comme décrit 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 joctee d'utiliser
hasattr
fonctionne dans la pratique, cela ne fonctionne vraiment que par accident carhasattr
supprime toutes les exceptions (y comprisDoesNotExist
) par opposition à justeAttributeError
s, comme il se doit. Comme l'a souligné Pi Delport , ce comportement a en fait été corrigé dans Python 3.2 par le ticket suivant: http://bugs.python.org/issue9666 . De plus - et au risque de paraître opiniâtre - je pense que letry
/except
construct ci - dessus est plus représentatif du fonctionnement de Django, tandis que l'utilisationhasattr
peut brouiller le problème pour les débutants, ce qui peut créer des FUD et propager de mauvaises habitudes.EDIT Le compromis raisonnable de Don Kirkby me semble également raisonnable.
la source
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 ont soulevé des inquiétudes quant au fait que cela pourrait ne pas fonctionner avec certaines versions de Python ou Django, mais la documentation de Django montre cette technique comme l'une des options:
>>> hasattr(p2, 'restaurant') False
Bien sûr, la documentation montre également la technique de capture d'exceptions:
>>> 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 pour dire que le fait d'attraper l'exception rend plus clair ce qui se passe, mais cela me semble juste plus compliqué. Peut-être s'agit-il d'un compromis raisonnable?
>>> print(Restaurant.objects.filter(place=p2).first()) None
Il s'agit simplement d'interroger les
Restaurant
objets par endroit. Il revientNone
si cet endroit n'a pas de restaurant.Voici un extrait d'exécutable pour que vous puissiez jouer avec les options. Si vous avez installé Python, Django et SQLite3, 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()
la source
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 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 associée (ici: request.user).
la source
Utilisez
select_related
!>>> user = User.objects.select_related('type1profile').get(pk=111) >>> user.type1profile None
la source
RelatedObjectDoesNotExist
.au cas où vous auriez le modèle
class UserProfile(models.Model): user = models.OneToOneField(User, unique=True)
Et vous avez juste besoin de savoir pour tout utilisateur que UserProfile existe / ou non - le moyen le plus efficace du point de vue de la base de données d'utiliser la requête existe .
La requête Exists renverra uniquement un booléen, plutôt qu'un accès inversé aux attributs comme
hasattr(request.user, 'type1profile')
- qui générera une requête get et retournera une représentation complète de l'objetPour ce faire, vous devez ajouter une propriété au modèle utilisateur
class User(AbstractBaseUser) @property def has_profile(): return UserProfile.objects.filter(user=self.pk).exists()
la source
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
la source
L'une des approches intelligentes sera d'ajouter le champ personnalisé OneToOneOrNoneField et de l' utiliser [fonctionne pour 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)
la mise en oeuvre
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
la source
SingleRelatedObjectDescriptor
plutôt queReverseOneToOneDescriptor
comme çafrom django.db.models.fields.related import SingleRelatedObjectDescriptor