Mam dwa takie modele:
class Type1Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
class Type2Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
Muszę coś zrobić, jeśli użytkownik ma profil Type1 lub Type2:
if request.user.type1profile != None:
# do something
elif request.user.type2profile != None:
# do something else
else:
# do something else
Jednak w przypadku użytkowników, którzy nie mają profilu typu 1 lub typu 2, wykonanie takiego kodu powoduje następujący błąd:
Type1Profile matching query does not exist.
Jak mogę sprawdzić typ profilu użytkownika?
Dzięki
python
django
django-models
one-to-one
John Bright
źródło
źródło
select_related()
teraz lub w przyszłości - a może nawet mieć pewność, że również obsługiwać inne rodzaje magii, które mogą się zdarzyć w innym miejscu - trzeba przedłużyć test następująco:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
hasattr
połknie wszystkie wyjątki, które zdarzają się podczas wyszukiwania bazy danych, a nie tylkoDoesNotExist
. To prawdopodobnie jest zepsute, a nie to, czego chcesz.Można sprawdzić, czy relacja jeden-do-jednego dopuszczająca wartość null jest zerowa dla określonego modelu, po prostu testując odpowiednią zmienną w modelu pod kątem
None
ness, ale tylko wtedy, gdy testujesz na modelu, z którego pochodzi relacja jeden do jednego. Na przykład biorąc pod uwagę te dwie klasy…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()
… Aby sprawdzić, czy a
Restaurant
ma aPlace
, możemy użyć następującego kodu:>>> 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!
Aby sprawdzić, czy a
Place
maRestaurant
, ważne jest, aby zrozumieć, że odwołanie się dorestaurant
właściwości w wystąpieniuPlace
wywołujeRestaurant.DoesNotExist
wyjątek, jeśli nie ma odpowiedniej restauracji. Dzieje się tak, ponieważ Django wykonuje wyszukiwanie wewnętrznie przy użyciuQuerySet.get()
. Na przykład:>>> 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.
W tym scenariuszu brzytwa Ockhama przeważa, a najlepszym podejściem do określenia, czy a
Place
ma, czy nie ,Restautrant
byłby standardtry
/except
konstrukcja opisana tutaj .>>> try: >>> restaurant = p2.restaurant >>> except Restaurant.DoesNotExist: >>> print "Place has no restaurant!" >>> else: >>> # Do something with p2's restaurant here.
Chociaż sugestia joctee dotycząca użycia
hasattr
działa w praktyce, tak naprawdę działa tylko przez przypadek, ponieważhasattr
blokuje wszystkie wyjątki (w tymDoesNotExist
), a nie tylkoAttributeError
s, tak jak powinno. Jak zauważył Pi Delport , to zachowanie zostało faktycznie poprawione w Pythonie 3.2 na następującym bilecie: http://bugs.python.org/issue9666 . Ponadto - i na ryzyko brzmiące uparty - Wierzę, że powyżejtry
/except
konstrukcja jest bardziej reprezentatywna jak Django działa podczas używaniahasattr
może zaciemniać problem dla początkujących, który może tworzyć FUD i rozprzestrzeniania złych nawyków.EDYCJA Rozsądny kompromis Dona Kirkby'ego również wydaje mi się rozsądny.
źródło
Podoba mi się odpowiedź joctee , ponieważ jest taka prosta.
if hasattr(request.user, 'type1profile'): # do something elif hasattr(request.user, 'type2profile'): # do something else else: # do something else
Inni komentatorzy wyrazili obawy, że może nie działać z niektórymi wersjami Pythona lub Django, ale dokumentacja Django przedstawia tę technikę jako jedną z opcji:
>>> hasattr(p2, 'restaurant') False
Oczywiście dokumentacja pokazuje również technikę przechwytywania wyjątków:
>>> from django.core.exceptions import ObjectDoesNotExist >>> try: >>> p2.restaurant >>> except ObjectDoesNotExist: >>> print("There is no restaurant here.") There is no restaurant here.
Zgadzam się z Joshuą, że wychwycenie wyjątku wyjaśnia, co się dzieje, ale wydaje mi się to bardziej nieczyste. Może to rozsądny kompromis?
>>> print(Restaurant.objects.filter(place=p2).first()) None
To po prostu odpytywanie
Restaurant
obiektów według miejsca. Wraca,None
jeśli w tym miejscu nie ma restauracji.Oto fragment kodu wykonywalnego, dzięki któremu możesz bawić się opcjami. Jeśli masz zainstalowane Python, Django i SQLite3, powinno po prostu działać. Przetestowałem to z Pythonem 2.7, Pythonem 3.4, Django 1.9.2 i 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()
źródło
Co powiesz na użycie bloków 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
Następnie użyj w ten sposób!
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!
Przypuszczam, że możesz użyć tego jako funkcji ogólnej, aby uzyskać dowolną odwrotną instancję OneToOne, biorąc pod uwagę klasę źródłową (tutaj: twoje klasy profilu) i powiązaną instancję (tutaj: request.user).
źródło
Użyj
select_related
!>>> user = User.objects.select_related('type1profile').get(pk=111) >>> user.type1profile None
źródło
RelatedObjectDoesNotExist
.jeśli masz Model
class UserProfile(models.Model): user = models.OneToOneField(User, unique=True)
I po prostu musisz wiedzieć dla każdego użytkownika, że UserProfile istnieje / lub nie - najskuteczniejszy sposób z punktu widzenia bazy danych na użycie zapytania o istnienie .
Zapytanie Exists zwróci tylko wartość logiczną, zamiast odwracać dostęp do atrybutów, jak
hasattr(request.user, 'type1profile')
- co spowoduje wygenerowanie zapytania get i zwrócenie pełnej reprezentacji obiektuAby to zrobić - musisz dodać właściwość do modelu użytkownika
class User(AbstractBaseUser) @property def has_profile(): return UserProfile.objects.filter(user=self.pk).exists()
źródło
Używam kombinacji has_attr i jest to Brak:
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
źródło
Jednym ze sprytnych podejść będzie dodanie niestandardowego pola OneToOneOrNoneField i użycie go [działa dla 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)
Realizacja
class Restaurant(models.Model): # The class where the one-to-one originates place = OneToOneOrNoneField(Place) serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField()
Stosowanie
r = Restaurant(serves_hot_dogs=True, serves_pizza=False) r.place # will return None
źródło
SingleRelatedObjectDescriptor
zamiastReverseOneToOneDescriptor
tegofrom django.db.models.fields.related import SingleRelatedObjectDescriptor