python - many - one to one django
Comprueba si OneToOneField es None en Django (5)
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 de tipo 1 o tipo 2, la ejecución de un código como ese produce el siguiente error:
Type1Profile matching query does not exist.
¿Cómo puedo verificar el tipo de perfil que tiene un usuario?
Gracias
¿Qué hay de usar try / except blocks?
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, usa 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 de OneToOne inversa, dada una clase de origen (aquí: sus clases de perfil) y una instancia relacionada (aquí: request.user).
Es posible ver si una relación anulable uno-a-uno es nula para un modelo en particular simplemente probando el campo correspondiente en el modelo para None
ness, 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 un Restaurant
tiene un Place
, 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 un Place
tiene un Restaurant
, es importante comprender que al hacer referencia a la propiedad del restaurant
en una instancia de Place
genera una excepción de Restaurant.DoesNotExist
si no hay un restaurante correspondiente. Esto sucede porque Django realiza una búsqueda internamente usando QuerySet.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, la navaja de afeitar de Occam prevalece, y el mejor enfoque para hacer una determinación sobre si un Place
tiene un Restautrant
sería una construcción de try
/ except
estándar como se describe here .
>>> 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 hasattr
para utilizar hasattr
funciona en la práctica, realmente solo funciona por accidente, ya que hasattr
suprime todas las excepciones (incluido DoesNotExist
) en lugar de simplemente AttributeError
, como debería ser. Como señaló Piet Delport, este comportamiento en realidad se corrigió en Python 3.2 por el siguiente ticket: http://bugs.python.org/issue9666 . Además, y a riesgo de sonar obstinado, creo que la construcción try
/ except
es más representativa de cómo funciona Django, mientras que usar hasattr
puede nublar el problema para los novatos, lo que puede crear FUD y propagar malos hábitos.
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 no funcione con ciertas versiones de Python o Django, pero la documentación de Django muestra esta técnica como una de las opciones:
También puede usar hasattr para evitar la necesidad de captura de excepciones:
>>> hasattr(p2, ''restaurant'')
False
Por supuesto, la documentación también muestra la técnica de captura de excepción:
p2 no tiene un restaurante asociado:
>>> 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 que la captura de la excepción aclara lo que está sucediendo, pero me parece más desordenado. Tal vez este es un compromiso razonable?
>>> print(Restaurant.objects.filter(place=p2).first())
None
Esto es solo consultar los objetos del Restaurant
por lugar. Devuelve None
si ese lugar no tiene restaurante.
Aquí hay un fragmento ejecutable para que juegues con las opciones. Si tiene Python, Django y SQLite3 instalados, 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()
Para verificar si la relación (OneToOne) existe o no, puede usar la función hasattr
:
if hasattr(request.user, ''type1profile''):
# do something
elif hasattr(request.user, ''type2profile''):
# do something else
else:
# do something else
Use select_related
!
>>> user = User.objects.select_related(''type1profile'').get(pk=111)
>>> user.type1profile
None