Periksa apakah OneToOneField Tidak Ada di Django

85

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

John Bright
sumber

Jawaban:

92

Untuk memeriksa apakah relasi (OneToOne) ada atau tidak, Anda dapat menggunakan hasattrfungsi:

if hasattr(request.user, 'type1profile'):
    # do something
elif hasattr(request.user, 'type2profile'):
    # do something else
else:
    # do something else
lelucon
sumber
4
Terima kasih atas solusi ini. Sayangnya, ini tidak selalu berhasil. Jika Anda ingin bekerja dengan 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
class stacker
7
Perhatikan bahwa di Python <3.2, hasattrakan menelan semua pengecualian yang terjadi selama pencarian database, dan tidak hanya DoesNotExist. Ini mungkin rusak, dan bukan yang Anda inginkan.
Pi Delport
tidak bekerja dengan python 2.7. Bahkan jika OneToOne tidak ada, ia mengembalikan objek django.db.models.fields.related.RelatedManager.
alexpirine
@alartur versi django apa yang anda gunakan?
joctee
Django 1.5. Tetapi saya memecahkan masalah khusus saya dengan menerapkan apa yang ingin saya lakukan dengan cara yang sama sekali berbeda.
alexpirine
48

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 Noneness, 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 Restaurantmemiliki Place, 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 Placememiliki a Restaurant, penting untuk dipahami bahwa mereferensikan restaurantproperti pada sebuah instance Placemenimbulkan Restaurant.DoesNotExistpengecualian jika tidak ada restoran yang sesuai. Ini terjadi karena Django melakukan pencarian secara internal menggunakan QuerySet.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 Placememiliki Restautrantakan menjadi standar try/ exceptkonstruksi 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 hasattrkarya dalam praktiknya, ini benar-benar hanya berfungsi secara tidak sengaja karena hasattrmenyembunyikan semua pengecualian (termasukDoesNotExist ) sebagai lawan hanya AttributeErrors, 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 atas try/ exceptkonstruksi 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.

Joshua Pokotilow
sumber
19

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:

Anda juga dapat menggunakan hasattr untuk menghindari perlunya penangkapan pengecualian:

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

Tentu saja, dokumentasi juga menunjukkan teknik penangkapan pengecualian:

p2 tidak memiliki restoran terkait:

>>> 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()
Don Kirkby
sumber
10

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).

Geradeausanwalt
sumber
3

Gunakan select_related!

>>> user = User.objects.select_related('type1profile').get(pk=111)
>>> user.type1profile
None
ivan133
sumber
2
Saya tahu ini berfungsi seperti ini, tetapi apakah perilaku select_related ini sebenarnya didokumentasikan?
Kos
3
Saya baru saja mencoba ini di Django 1.9.2, dan muncul RelatedObjectDoesNotExist.
Don Kirkby
1

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 penuh

Untuk melakukannya - Anda perlu menambahkan properti ke model User

class User(AbstractBaseUser)

@property
def has_profile():
    return UserProfile.objects.filter(user=self.pk).exists()
pymen
sumber
0

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
FreeWorlder
sumber
0

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
pymen
sumber
untuk django 1.8 Anda perlu menggunakan SingleRelatedObjectDescriptoralih-alih ReverseOneToOneDescriptorseperti ini from django.db.models.fields.related import SingleRelatedObjectDescriptor
pymen