Tenho dois modelos como este:
class Type1Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
class Type2Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
Preciso fazer algo se o usuário tiver perfil Tipo1 ou Tipo2:
if request.user.type1profile != None:
# do something
elif request.user.type2profile != None:
# do something else
else:
# do something else
Mas, para usuários que não têm perfis type1 ou type2, executar um código como esse produz o seguinte erro:
Type1Profile matching query does not exist.
Como posso verificar o tipo de perfil de um usuário?
obrigado
python
django
django-models
one-to-one
John Bright
fonte
fonte
select_related()
agora ou no futuro - ou talvez até mesmo para ter certeza de que também lida com outros tipos de magia que podem acontecer em outro lugar - você deve estender o teste da seguinte forma:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
hasattr
irá engolir todas as exceções que acontecem durante a consulta ao banco de dados, e não apenasDoesNotExist
. Provavelmente está quebrado e não é o que você deseja.É possível ver se um relacionamento um-para-um anulável é nulo para um modelo específico simplesmente testando o campo correspondente no modelo para
None
ness, mas apenas se você testar no modelo onde o relacionamento um-para-um se origina. Por exemplo, dadas essas duas 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()
... para ver se a
Restaurant
tem umPlace
, podemos usar o seguinte 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 se a
Place
tem umRestaurant
, é importante entender que fazer referência àrestaurant
propriedade em uma instância dePlace
gera umaRestaurant.DoesNotExist
exceção se não houver restaurante correspondente. Isso acontece porque o Django realiza uma pesquisa internamente usandoQuerySet.get()
. Por exemplo:>>> 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.
Nesse cenário, a navalha de Occam prevalece e a melhor abordagem para determinar se a
Place
tem ou nãoRestautrant
é um padrãotry
/except
construção conforme descrito aqui .>>> try: >>> restaurant = p2.restaurant >>> except Restaurant.DoesNotExist: >>> print "Place has no restaurant!" >>> else: >>> # Do something with p2's restaurant here.
Embora a sugestão de Joctee de usar
hasattr
funcione na prática, ela realmente só funciona por acidente, uma vez quehasattr
suprime todas as exceções (inclusiveDoesNotExist
) em oposição a apenasAttributeError
s, como deveria. Como Pi Delport apontou, esse comportamento foi realmente corrigido no Python 3.2 de acordo com o seguinte tíquete: http://bugs.python.org/issue9666 . Além disso - e correndo o risco de soar opinativo - acredito que o construtotry
/ acimaexcept
é mais representativo de como o Django funciona, enquanto o usohasattr
pode obscurecer o problema para iniciantes, o que pode criar FUD e espalhar maus hábitos.EDITAR O compromisso razoável de Don Kirkby também me parece razoável.
fonte
Gosto da resposta de Joctee , porque é tão simples.
if hasattr(request.user, 'type1profile'): # do something elif hasattr(request.user, 'type2profile'): # do something else else: # do something else
Outros comentadores levantaram preocupações de que pode não funcionar com certas versões do Python ou Django, mas a documentação do Django mostra essa técnica como uma das opções:
>>> hasattr(p2, 'restaurant') False
Claro, a documentação também mostra a técnica de captura de exceção:
>>> from django.core.exceptions import ObjectDoesNotExist >>> try: >>> p2.restaurant >>> except ObjectDoesNotExist: >>> print("There is no restaurant here.") There is no restaurant here.
Eu concordo com o josué que capturar a exceção torna mais claro o que está acontecendo, mas parece mais confuso para mim. Talvez este seja um compromisso razoável?
>>> print(Restaurant.objects.filter(place=p2).first()) None
Isso é apenas consultar o
Restaurant
objetos por local. Ele retornaNone
se aquele lugar não tiver restaurante.Aqui está um snippet executável para você brincar com as opções. Se você tiver Python, Django e SQLite3 instalados, ele deve apenas ser executado. Eu testei com Python 2.7, Python 3.4, Django 1.9.2 e 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()
fonte
Que tal usar os blocos 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
Então, use assim!
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!
Suponho que você possa usar isso como uma função genérica para obter qualquer instância OneToOne reversa, dada uma classe de origem (aqui: suas classes de perfil) e uma instância relacionada (aqui: request.user).
fonte
Use
select_related
!>>> user = User.objects.select_related('type1profile').get(pk=111) >>> user.type1profile None
fonte
RelatedObjectDoesNotExist
.no caso de você ter o modelo
class UserProfile(models.Model): user = models.OneToOneField(User, unique=True)
E você só precisa saber para qualquer usuário que UserProfile existe / ou não - a forma mais eficiente do ponto de vista de banco de dados para usar a consulta existe .
A consulta existente retornará apenas booleano, em vez de acesso reverso ao atributo, como
hasattr(request.user, 'type1profile')
- o que irá gerar obter a consulta e retornar a representação completa do objetoPara fazer isso, você precisa adicionar uma propriedade ao modelo de usuário
class User(AbstractBaseUser) @property def has_profile(): return UserProfile.objects.filter(user=self.pk).exists()
fonte
Estou usando uma combinação de has_attr e nenhum:
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
fonte
Uma das abordagens inteligentes será adicionar o campo personalizado OneToOneOrNoneField e usá- lo [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)
Implementação
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
fonte
SingleRelatedObjectDescriptor
invésReverseOneToOneDescriptor
destefrom django.db.models.fields.related import SingleRelatedObjectDescriptor