2010-08-11 18 views
61

Tengo dos modelos como este:Comprobar si OneToOneField hay ninguno en Django

class Type1Profile(models.Model): 
    user = models.OneToOneField(User, unique=True) 
    ... 


class Type2Profile(models.Model): 
    user = models.OneToOneField(User, unique=True) 
    ... 

tengo que hacer algo si el usuario dispone de Tipo 1 o Tipo 2 Perfil:

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 tener ya sea tipo 1 o perfiles tipo2, la ejecución de código como el que produce el siguiente error:

Type1Profile matching query does not exist. 

¿Cómo puedo comprobar el t ¿Qué perfil tiene un usuario?

Gracias

Respuesta

9

¿Qué le parece 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, utiliza 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 se podría utilizar esto como una función genérica para obtener cualquier instancia OneToOne inversa, dada una clase de origen (en este caso: las clases de perfil) y una instancia relacionada (aquí: request.user).

60

Para comprobar si existe el (OneToOne) relación o no, puede utilizar la función hasattr:

if hasattr(request.user, 'type1profile'): 
    # do something 
elif hasattr(request.user, 'type2profile'): 
    # do something else 
else: 
    # do something else 
+2

Gracias por esta solución. Desafortunadamente, esto no funciona todo el tiempo. En caso de que desee trabajar con 'select_related()' ahora o en el futuro, o tal vez para asegurarse de que también maneja otros tipos de magia que pueden ocurrir en cualquier otro lugar, debe extender la prueba de la siguiente manera: 'if hasattr (object, 'onetoonerevrelattr') y object.onetoonerevrelattr! = None' –

+6

Tenga en cuenta que en Python <3.2, 'hasattr' tragará * todas * las excepciones que ocurran durante la búsqueda de la base de datos, y no solo' DoesNotExist'. Esto probablemente esté roto, y no es lo que quieres. –

+0

no funciona con python 2.7. Incluso si OneToOne no existe, devuelve un objeto django.db.models.fields.related.RelatedManager. – alexpirine

3

Uso select_related!

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

Sé que funciona así, pero ¿está documentado este comportamiento de select_related? – Kos

+1

Acabo de probar esto en Django 1.9.2, y plantea 'RelatedObjectDoesNotExist'. –

33

Es posible ver si un anulable uno-a-uno es nulo para un modelo en particular, simplemente probando el campo correspondiente en el modelo para None Ness, pero sólosi se prueba en el modelo donde la relación uno-a-uno se origina. Por ejemplo, teniendo en cuenta 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 para comprender que al hacer referencia a la propiedad restaurant en una instancia de Place se genera una excepción 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 Occam prevalece, y el mejor enfoque para hacer una determinación sobre si o no un Place tiene un Restautrant habría un estándar try/except constructo como se describe here.

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

Aunque la sugerencia de utilizar joctee hasattr funciona en la práctica, lo que realmente sólo funciona por accidente ya que suprime hasattr todas las excepciones (incluyendo DoesNotExist) en lugar de sólo AttributeError s, como debería.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 con el riesgo de parecer testarudo, creo que la construcción anterior try/except es más representativa de cómo funciona Django, mientras que el uso de hasattr puede nublar el problema para los novatos, lo que puede crear FUD y difundir malos hábitos.

11

Me gusta joctee's answer, 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 puede trabajar con ciertas versiones de Python o Django, pero the Django documentation muestra esta técnica como una de las opciones:

You can also use hasattr to avoid the need for exception catching:

>>> hasattr(p2, 'restaurant') 
False 

Por supuesto, el La documentación también muestra la técnica de captura de excepción:

p2 doesn’t have an associated restaurant:

>>> 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 el Restaurant objetos por lugar. Devuelve None si ese lugar no tiene restaurante.

Aquí hay un fragmento ejecutable para que juegue 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() 
Cuestiones relacionadas