Saya punya dua model seperti ini:
class Type1Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
class Type2Profile(models.Model):
user = models.OneToOneField(User, unique=True)
...
Saya perlu melakukan sesuatu jika pengguna memiliki profil Type1 atau Type2:
if request.user.type1profile != None:
# do something
elif request.user.type2profile != None:
# do something else
else:
# do something else
Namun, untuk pengguna yang tidak memiliki profil tipe1 atau tipe2, menjalankan kode seperti itu menghasilkan kesalahan berikut:
Type1Profile matching query does not exist.
Bagaimana cara memeriksa jenis profil yang dimiliki pengguna?
Terima kasih
python
django
django-models
one-to-one
John Bright
sumber
sumber
select_related()
sekarang atau di masa depan - atau mungkin bahkan untuk memastikan Anda juga menangani sihir jenis lain yang mungkin terjadi di tempat lain - Anda harus memperpanjang tes sebagai berikut:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
hasattr
akan menelan semua pengecualian yang terjadi selama pencarian database, dan tidak hanyaDoesNotExist
. Ini mungkin rusak, dan bukan yang Anda inginkan.Mungkin untuk melihat apakah hubungan satu-ke-satu yang dapat dinolkan adalah nol untuk model tertentu hanya dengan menguji bidang yang sesuai pada model untuk
None
ness, tetapi hanya jika Anda menguji pada model tempat hubungan satu-ke-satu berasal. Misalnya, dengan dua kelas ini…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()
… Untuk melihat apakah a
Restaurant
memilikiPlace
, kita dapat menggunakan kode berikut:>>> 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!
Untuk mengetahui apakah a
Place
memiliki aRestaurant
, penting untuk dipahami bahwa mereferensikanrestaurant
properti pada sebuah instancePlace
menimbulkanRestaurant.DoesNotExist
pengecualian jika tidak ada restoran yang sesuai. Ini terjadi karena Django melakukan pencarian secara internal menggunakanQuerySet.get()
. Sebagai contoh:>>> 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.
Dalam skenario ini, pisau cukur Occam berlaku, dan pendekatan terbaik untuk membuat keputusan tentang apakah a
Place
memilikiRestautrant
akan menjadi standartry
/except
konstruksi seperti yang dijelaskan di sini .>>> try: >>> restaurant = p2.restaurant >>> except Restaurant.DoesNotExist: >>> print "Place has no restaurant!" >>> else: >>> # Do something with p2's restaurant here.
Meskipun saran joctee untuk menggunakan
hasattr
karya dalam praktiknya, ini benar-benar hanya berfungsi secara tidak sengaja karenahasattr
menyembunyikan semua pengecualian (termasukDoesNotExist
) sebagai lawan hanyaAttributeError
s, seperti yang seharusnya. Seperti yang ditunjukkan Pi Delport , perilaku ini sebenarnya diperbaiki dengan Python 3.2 sesuai tiket berikut: http://bugs.python.org/issue9666 . Lebih jauh - dan dengan resiko terdengar beropini - saya percaya di atastry
/except
konstruksi lebih mewakili bagaimana Django bekerja, sambil menggunakanhasattr
dapat mengaburkan masalah untuk pemula, yang dapat menciptakan FUD dan menyebarkan kebiasaan buruk.EDIT Kompromi Don Kirkby yang masuk akal juga tampak masuk akal bagi saya.
sumber
Saya suka jawaban joctee , karena sangat sederhana.
if hasattr(request.user, 'type1profile'): # do something elif hasattr(request.user, 'type2profile'): # do something else else: # do something else
Pemberi komentar lain telah mengemukakan kekhawatiran bahwa ini mungkin tidak bekerja dengan versi Python atau Django tertentu, tetapi dokumentasi Django memperlihatkan teknik ini sebagai salah satu opsi:
>>> hasattr(p2, 'restaurant') False
Tentu saja, dokumentasi juga menunjukkan teknik penangkapan pengecualian:
>>> from django.core.exceptions import ObjectDoesNotExist >>> try: >>> p2.restaurant >>> except ObjectDoesNotExist: >>> print("There is no restaurant here.") There is no restaurant here.
Saya setuju dengan Joshua bahwa menangkap pengecualian membuatnya lebih jelas apa yang terjadi, tetapi tampaknya lebih berantakan bagi saya. Mungkinkah ini kompromi yang masuk akal?
>>> print(Restaurant.objects.filter(place=p2).first()) None
Ini hanya menanyakan
Restaurant
objek berdasarkan tempat. Ia kembaliNone
jika tempat itu tidak memiliki restoran.Berikut cuplikan yang dapat dieksekusi untuk Anda mainkan dengan opsi. Jika Anda memiliki Python, Django, dan SQLite3 terinstal, itu harus dijalankan. Saya mengujinya dengan Python 2.7, Python 3.4, Django 1.9.2, dan 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()
sumber
Bagaimana kalau menggunakan blok try / kecuali?
def get_profile_or_none(user, profile_cls): try: profile = getattr(user, profile_cls.__name__.lower()) except profile_cls.DoesNotExist: profile = None return profile
Lalu, gunakan seperti ini!
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!
Saya kira Anda dapat menggunakan ini sebagai fungsi umum untuk mendapatkan instance OneToOne terbalik, diberi kelas yang berasal (di sini: kelas profil Anda) dan instance terkait (di sini: request.user).
sumber
Gunakan
select_related
!>>> user = User.objects.select_related('type1profile').get(pk=111) >>> user.type1profile None
sumber
RelatedObjectDoesNotExist
.jika Anda memiliki Model
class UserProfile(models.Model): user = models.OneToOneField(User, unique=True)
Dan Anda hanya perlu mengetahui untuk setiap Pengguna bahwa UserProfile ada / atau tidak - cara paling efisien dari sudut pandang database untuk menggunakan kueri yang ada. .
Kueri yang ada hanya akan mengembalikan boolean, bukan membalikkan akses atribut seperti
hasattr(request.user, 'type1profile')
- yang akan menghasilkan kueri get dan mengembalikan representasi objek penuhUntuk melakukannya - Anda perlu menambahkan properti ke model User
class User(AbstractBaseUser) @property def has_profile(): return UserProfile.objects.filter(user=self.pk).exists()
sumber
Saya menggunakan kombinasi has_attr dan 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
sumber
Salah satu pendekatan cerdas akan menambahkan bidang kustom OneToOneOrNoneField dan menggunakannya [bekerja untuk 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)
Penerapan
class Restaurant(models.Model): # The class where the one-to-one originates place = OneToOneOrNoneField(Place) serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField()
Pemakaian
r = Restaurant(serves_hot_dogs=True, serves_pizza=False) r.place # will return None
sumber
SingleRelatedObjectDescriptor
alih-alihReverseOneToOneDescriptor
seperti inifrom django.db.models.fields.related import SingleRelatedObjectDescriptor