Tengo dos modelos como este:
class Type1Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
class Type2Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
Necesito hacer algo si el usuario tiene un perfil Type1 o Type2:
if request.user.type1profile != None:
# do something
elif request.user.type2profile != None:
# do something else
else:
# do something else
Pero, para los usuarios que no tienen perfiles type1 o type2, ejecutar un código como ese produce el siguiente error:
Type1Profile matching query does not exist.
¿Cómo puedo comprobar el tipo de perfil que tiene un usuario?
Gracias
python
django
django-models
one-to-one
John Bright
fuente
fuente
select_related()
ahora o en el futuro, o tal vez incluso para estar seguro de que también maneja otros tipos de magia que pueden suceder en otros lugares, debe extender la prueba de la siguiente manera:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
hasattr
se tragarán todas las excepciones que sucedan durante la búsqueda de la base de datos, y no soloDoesNotExist
. Probablemente esté roto, y no es lo que quieres.Es posible ver si una relación uno a uno que acepta valores NULL es nula para un modelo en particular simplemente probando el campo correspondiente en el modelo
None
, pero solo si prueba en el modelo donde se origina la relación uno a uno. Por ejemplo, dadas estas dos clases ...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()
... para ver si a
Restaurant
tiene unPlace
, podemos usar el siguiente código:>>> 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!
Para ver si a
Place
tiene unRestaurant
, es importante comprender que hacer referencia a larestaurant
propiedad en una instancia dePlace
genera unaRestaurant.DoesNotExist
excepción si no hay un restaurante correspondiente. Esto sucede porque Django realiza una búsqueda internamente usandoQuerySet.get()
. Por ejemplo:>>> 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.
En este escenario, prevalece la navaja de Occam, y el mejor enfoque para tomar una determinación sobre si un
Place
tiene o noRestautrant
sería un estándartry
/except
construcción como se describe aquí .>>> try: >>> restaurant = p2.restaurant >>> except Restaurant.DoesNotExist: >>> print "Place has no restaurant!" >>> else: >>> # Do something with p2's restaurant here.
Si bien la sugerencia de joctee de usar
hasattr
funciona en la práctica, en realidad solo funciona por accidente, ya quehasattr
suprime todas las excepciones (incluidasDoesNotExist
) en lugar de soloAttributeError
s, como debería. Como señaló Pi Delport , este comportamiento en realidad se corrigió en Python 3.2 según el siguiente ticket: http://bugs.python.org/issue9666 . Además, y a riesgo de parecer obstinado, creo que la construccióntry
/ anteriorexcept
es más representativa de cómo funciona Django, mientras que el usohasattr
puede nublar el problema para los principiantes, lo que puede crear FUD y difundir malos hábitos.EDITAR El compromiso razonable de Don Kirkby también me parece razonable.
fuente
Me gusta la respuesta de joctee , porque es muy simple.
if hasattr(request.user, 'type1profile'): # do something elif hasattr(request.user, 'type2profile'): # do something else else: # do something else
Otros comentaristas han expresado su preocupación de que es posible que no funcione con ciertas versiones de Python o Django, pero la documentación de Django muestra esta técnica como una de las opciones:
>>> hasattr(p2, 'restaurant') False
Por supuesto, la documentación también muestra la técnica de captura de excepciones:
>>> from django.core.exceptions import ObjectDoesNotExist >>> try: >>> p2.restaurant >>> except ObjectDoesNotExist: >>> print("There is no restaurant here.") There is no restaurant here.
Estoy de acuerdo con Joshua en que detectar la excepción aclara lo que está sucediendo, pero me parece más complicado. ¿Quizás este sea un compromiso razonable?
>>> print(Restaurant.objects.filter(place=p2).first()) None
Esto es solo consultar los
Restaurant
objetos por lugar. VuelveNone
si ese lugar no tiene restaurante.Aquí hay un fragmento ejecutable para que juegues con las opciones. Si tiene Python, Django y SQLite3 instalados, simplemente debería ejecutarse. Lo probé con Python 2.7, Python 3.4, Django 1.9.2 y 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()
fuente
¿Qué tal usar bloques 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
¡Entonces, úsala así!
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!
Supongo que podría usar esto como una función genérica para obtener cualquier instancia inversa de OneToOne, dada una clase de origen (aquí: sus clases de perfil) y una instancia relacionada (aquí: request.user).
fuente
Utilice
select_related
!>>> user = User.objects.select_related('type1profile').get(pk=111) >>> user.type1profile None
fuente
RelatedObjectDoesNotExist
.en caso de que tengas el modelo
class UserProfile(models.Model): user = models.OneToOneField(User, unique=True)
Y solo necesita saber para cualquier usuario que UserProfile existe / o no, la forma más eficiente desde el punto de vista de la base de datos para usar la consulta existente .
La consulta existente devolverá solo un booleano, en lugar de un acceso de atributo inverso como
hasattr(request.user, 'type1profile')
, lo que generará una consulta de obtención y devolverá la representación completa del objetoPara hacerlo, debe agregar una propiedad al modelo de usuario
class User(AbstractBaseUser) @property def has_profile(): return UserProfile.objects.filter(user=self.pk).exists()
fuente
Estoy usando una combinación de has_attr y es 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
fuente
Uno de los enfoques inteligentes será agregar el campo personalizado OneToOneOrNoneField y usarlo [funciona para 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)
Implementación
class Restaurant(models.Model): # The class where the one-to-one originates place = OneToOneOrNoneField(Place) serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField()
Uso
r = Restaurant(serves_hot_dogs=True, serves_pizza=False) r.place # will return None
fuente
SingleRelatedObjectDescriptor
lugar deReverseOneToOneDescriptor
estofrom django.db.models.fields.related import SingleRelatedObjectDescriptor