У меня две такие модели:
class Type1Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
class Type2Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
Мне нужно что-то сделать, если у пользователя есть профиль Type1 или Type2:
if request.user.type1profile != None:
# do something
elif request.user.type2profile != None:
# do something else
else:
# do something else
Но для пользователей, у которых нет профилей type1 или type2, выполнение такого кода приводит к следующей ошибке:
Type1Profile matching query does not exist.
Как я могу проверить тип профиля пользователя?
Благодарность
python
django
django-models
one-to-one
Джон Брайт
источник
источник
select_related()
сейчас или в будущем - или, может быть, даже чтобы убедиться, что вы также справляетесь с другими видами магии, которые могут произойти где-то еще - вам нужно расширить тест следующим образом:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
hasattr
будут проглатываться все исключения, возникающие во время поиска в базе данных, а не толькоDoesNotExist
. Вероятно, это сломано, а не то, что вам нужно.Можно увидеть, является ли обнуляемое отношение «один-к-одному» нулевым для конкретной модели, просто проверив соответствующее поле в модели на предмет соответствия
None
, но только если вы протестируете модель, в которой возникает однозначное отношение. Например, учитывая эти два класса…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()
… Чтобы увидеть, есть ли
Restaurant
у aPlace
, мы можем использовать следующий код:>>> 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!
Чтобы узнать, есть ли
Place
у aRestaurant
, важно понимать, что ссылка наrestaurant
свойство в экземпляреPlace
вызываетRestaurant.DoesNotExist
исключение, если нет соответствующего ресторана. Это происходит потому, что Django выполняет внутренний поиск с использованиемQuerySet.get()
. Например:>>> 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.
В этом сценарии преобладает бритва Оккама, и лучшим подходом для определения того, есть ли у
Place
a,Restautrant
будет стандартtry
/except
конструкция, как описано здесь .>>> try: >>> restaurant = p2.restaurant >>> except Restaurant.DoesNotExist: >>> print "Place has no restaurant!" >>> else: >>> # Do something with p2's restaurant here.
Хотя предложение joctee использовать
hasattr
на практике работает, на самом деле оно работает только случайно, посколькуhasattr
подавляет все исключения (в том числеDoesNotExist
), а не простоAttributeError
s, как должно. Как отметил Пи Делпорт , это поведение было фактически исправлено в Python 3.2 по следующему тикету: http://bugs.python.org/issue9666 . Более того - и рискуя показаться самоуверенным - я считаю, что приведенная выше конструкцияtry
/except
более репрезентативна для того, как работает Django, в то время как использованиеhasattr
может скрыть проблему для новичков, что может создать FUD и распространить вредные привычки.РЕДАКТИРОВАТЬ Разумный компромисс Дона Киркби также кажется мне разумным.
источник
Мне нравится ответ Джокти , потому что он такой простой.
if hasattr(request.user, 'type1profile'): # do something elif hasattr(request.user, 'type2profile'): # do something else else: # do something else
Другие комментаторы высказали опасения, что он может не работать с некоторыми версиями Python или Django, но в документации Django этот метод показан как один из вариантов:
>>> hasattr(p2, 'restaurant') False
Конечно, в документации также показан метод перехвата исключений:
>>> from django.core.exceptions import ObjectDoesNotExist >>> try: >>> p2.restaurant >>> except ObjectDoesNotExist: >>> print("There is no restaurant here.") There is no restaurant here.
Я согласен с Джошуа в том, что перехват исключения делает более ясным, что происходит, но мне это кажется более беспорядочным. Может, это разумный компромисс?
>>> print(Restaurant.objects.filter(place=p2).first()) None
Это просто запрос
Restaurant
объектов по месту. Он возвращается,None
если в этом месте нет ресторана.Вот исполняемый фрагмент кода, чтобы вы могли поиграть с опциями. Если у вас установлены Python, Django и SQLite3, он должен просто запуститься. Я тестировал его с Python 2.7, Python 3.4, Django 1.9.2 и 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()
источник
Как насчет использования блоков 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
Тогда используйте вот так!
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!
Я полагаю, вы могли бы использовать это как общую функцию для получения любого обратного экземпляра OneToOne, учитывая исходный класс (здесь: классы вашего профиля) и связанный экземпляр (здесь: request.user).
источник
Используйте
select_related
!>>> user = User.objects.select_related('type1profile').get(pk=111) >>> user.type1profile None
источник
RelatedObjectDoesNotExist
.если у вас есть модель
class UserProfile(models.Model): user = models.OneToOneField(User, unique=True)
И вам просто нужно знать для любого пользователя, что UserProfile существует / или нет - наиболее эффективный способ с точки зрения базы данных использовать существующий запрос .
Запрос Exists вернет только логическое значение, а не обратный доступ к атрибутам, например
hasattr(request.user, 'type1profile')
- который будет генерировать запрос и возвращать полное представление объекта.Для этого нужно добавить свойство в модель User.
class User(AbstractBaseUser) @property def has_profile(): return UserProfile.objects.filter(user=self.pk).exists()
источник
Я использую комбинацию has_attr и 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
источник
Один из разумных подходов - добавить настраиваемое поле OneToOneOrNoneField и использовать его [работает для 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)
Реализация
class Restaurant(models.Model): # The class where the one-to-one originates place = OneToOneOrNoneField(Place) serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField()
Применение
r = Restaurant(serves_hot_dogs=True, serves_pizza=False) r.place # will return None
источник
SingleRelatedObjectDescriptor
вместоReverseOneToOneDescriptor
этогоfrom django.db.models.fields.related import SingleRelatedObjectDescriptor