OneToOneField'ın Django'da Yok olup olmadığını kontrol edin


85

Bunun gibi iki modelim var:

class Type1Profile(models.Model):
    user = models.OneToOneField(User, unique=True)
    ...


class Type2Profile(models.Model):
    user = models.OneToOneField(User, unique=True)
    ...

Kullanıcının Type1 veya Type2 profili varsa bir şeyler yapmam gerekiyor:

if request.user.type1profile != None:
    # do something
elif request.user.type2profile != None:
    # do something else
else:
    # do something else

Ancak, tür1 veya tür2 profillerine sahip olmayan kullanıcılar için, böyle bir kod çalıştırmak aşağıdaki hatayı üretir:

Type1Profile matching query does not exist.

Bir kullanıcının sahip olduğu profil türünü nasıl kontrol edebilirim?

Teşekkürler

Yanıtlar:


92

(OneToOne) ilişkisinin var olup olmadığını kontrol etmek için hasattrişlevi kullanabilirsiniz :

if hasattr(request.user, 'type1profile'):
    # do something
elif hasattr(request.user, 'type2profile'):
    # do something else
else:
    # do something else

4
Bu çözüm için teşekkür ederim. Ne yazık ki bu her zaman işe yaramıyor. select_related()Şimdi veya gelecekte çalışmak istiyorsanız - ya da belki başka bir yerde olabilecek başka tür sihirleri de idare ettiğinizden emin olmak için - testi aşağıdaki gibi uzatmanız gerekir:if hasattr(object, 'onetoonerevrelattr') and object.onetoonerevrelattr != None
sınıf istifleyici

7
Python <3.2, unutmayın hasattryutacak tüm veritabanı araması sırasında meydana istisnalar ve sadece DoesNotExist. Bu muhtemelen bozuldu ve istediğin şey değil.
Pi Delport

python 2.7 ile çalışmıyor. OneToOne mevcut olmasa bile, bir django.db.models.fields.related.RelatedManager nesnesi döndürür.
alexpirine

@alartur hangi django sürümünü kullanıyorsunuz?
joctee

Django 1.5. Ama yapmak istediğim şeyi tamamen farklı bir şekilde uygulayarak sorunumu çözdüm.
alexpirine

48

Belirli bir model için boş değer atanabilir bire bir ilişkinin boş olup olmadığını görmek mümkündür, bunun için modeldeki karşılık gelen alanı Noneness için test ederek , ancak yalnızca bire bir ilişkinin ortaya çıktığı modeli test ederseniz. Örneğin, bu iki sınıf verildiğinde ...

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

… A'nın a Restaurantolup olmadığını görmek Placeiçin aşağıdaki kodu kullanabiliriz:

>>> 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!

A'nın Placesahip olup olmadığını görmek için Restaurant, restaurantbir örneğinde mülke başvurmanın , karşılık gelen bir restoran yoksa Placebir Restaurant.DoesNotExististisna oluşturduğunu anlamak önemlidir . Bunun nedeni, Django'nun dahili olarak QuerySet.get(). Örneğin:

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

Bu senaryoda, Occam'ın tıraş bıçağı hakimdir ve bir Placesahip olup olmadığına dair bir karar vermek için en iyi yaklaşım burada açıklandığı gibi Restautrantbir standart try/ exceptyapı olacaktır .

>>> try:
>>>     restaurant = p2.restaurant
>>> except Restaurant.DoesNotExist:
>>>     print "Place has no restaurant!"
>>> else:
>>>     # Do something with p2's restaurant here.

Joctee'in hasattreserleri pratikte kullanma önerisi , olması gerektiği gibi, tüm istisnaları (dahil ) hasattrbastırdığı için gerçekten sadece tesadüfen işe yarar. Pi Delport'un belirttiği gibi , bu davranış aslında aşağıdaki bilete göre Python 3.2'de düzeltildi: http://bugs.python.org/issue9666 . Dahası - ve görüşlü görünme riski altında - yukarıdaki / yapının Django'nun nasıl çalıştığını daha iyi temsil ettiğine inanıyorum , ancak kullanımı yeni başlayanlar için sorunu bulanıklaştırabilir, bu da FUD yaratabilir ve kötü alışkanlıklar yayabilir.DoesNotExistAttributeErrortryexcepthasattr

DÜZENLE Don Kirkby'nin makul uzlaşması da bana mantıklı geliyor.


19

Joctee'in cevabını beğendim çünkü çok basit.

if hasattr(request.user, 'type1profile'):
    # do something
elif hasattr(request.user, 'type2profile'):
    # do something else
else:
    # do something else

Diğer yorumcular, Python veya Django'nun belirli sürümleriyle çalışmayabileceği konusunda endişelerini dile getirdiler, ancak Django belgeleri bu tekniği seçeneklerden biri olarak gösteriyor:

İstisna yakalama ihtiyacını önlemek için hasattr'ı da kullanabilirsiniz:

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

Elbette, belgeler ayrıca istisna yakalama tekniğini de gösterir:

p2'nin ilişkili bir restoranı yok:

>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>>     p2.restaurant
>>> except ObjectDoesNotExist:
>>>     print("There is no restaurant here.")
There is no restaurant here.

Joshua'nın istisnayı yakalamanın ne olduğunu daha net hale getirdiği konusunda hemfikirim , ama bana daha karışık geliyor. Belki de bu makul bir uzlaşmadır?

>>> print(Restaurant.objects.filter(place=p2).first())
None

Bu sadece Restaurantnesneleri yerlerine göre sorgulamaktır . O Noneyerde restoran yoksa geri döner .

İşte seçeneklerle oynamanız için çalıştırılabilir bir pasaj. Python, Django ve SQLite3 yüklüyse, sadece çalışmalıdır. Python 2.7, Python 3.4, Django 1.9.2 ve SQLite3 3.8.2 ile test ettim.

# 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()

10

Try / exclude blokları kullanmaya ne dersiniz?

def get_profile_or_none(user, profile_cls):

    try:
        profile = getattr(user, profile_cls.__name__.lower())
    except profile_cls.DoesNotExist:
        profile = None

    return profile

Öyleyse böyle kullanın!

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!

Sanırım bunu, bir kaynak sınıf (burada: profil sınıflarınız) ve ilgili bir örnek (burada: request.user) verilen herhangi bir ters OneToOne örneğini almak için genel bir işlev olarak kullanabilirsiniz.


3

Kullanın select_related!

>>> user = User.objects.select_related('type1profile').get(pk=111)
>>> user.type1profile
None

2
Bunun böyle çalıştığını biliyorum, ancak bu select_related davranışı gerçekten belgelenmiş mi?
Kos

3
Bunu Django 1.9.2'de denedim ve yükseliyor RelatedObjectDoesNotExist.
Don Kirkby

1

Modele sahipseniz

class UserProfile(models.Model):
    user = models.OneToOneField(User, unique=True)

Ve herhangi bir Kullanıcı için UserProfile'ın var olduğunu / olmadığını bilmeniz gerekir - veritabanı açısından bakıldığında en etkili yol var sorgudur .

Sorgu sadece boolean yerine dönecektir Var ters nitelik erişimi gibi hasattr(request.user, 'type1profile')üretecektir hangi - olsun sorgu ve tam nesne temsilini döndürür

Bunu yapmak için - Kullanıcı modeline bir mülk eklemeniz gerekir

class User(AbstractBaseUser)

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

0

Has_attr kombinasyonunu kullanıyorum ve Hiçbiri:

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

0

Biri akıllı yaklaşımlar eklemek olacaktır özel alanını OneToOneOrNoneField ve kullanmak [Django> = 1.9 için çalışmalarını] o

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)

Uygulama

class Restaurant(models.Model):  # The class where the one-to-one originates
    place = OneToOneOrNoneField(Place)
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()

Kullanım

r = Restaurant(serves_hot_dogs=True, serves_pizza=False)
r.place  # will return None

Django 1.8 için kullanmak gerek SingleRelatedObjectDescriptoryerine ReverseOneToOneDescriptorböyle from django.db.models.fields.related import SingleRelatedObjectDescriptor
pymen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.