Как use_for_related_fields работает в Django? - PullRequest
17 голосов
/ 20 мая 2011

Я не могу понять это из документов. Мне совершенно неясно, а точнее:

  • Это глобальная настройка? Поэтому, если я укажу этот атрибут в одном из менеджеров моделей, будет ли он использоваться глобально всеми классами моделей?
  • Если это не глобальная настройка, то какие именно отношения будут затронуты?
  • Возможно ли иметь одного менеджера модели для одного отношения и другого для другого отношения с той же моделью?

Больше всего я был бы признателен за любые хорошие примеры минимального использования, так как в документации их нет. Спасибо.

Ответы [ 2 ]

49 голосов
/ 20 мая 2011

Это глобальная настройка? Поэтому, если я укажу этот атрибут в одном из менеджеров моделей, будет ли он использоваться глобально всеми классами моделей?

Если я понял, что вы подразумеваете под глобальным, ответ - нет. Он будет использоваться для класса, только если он установлен менеджером по умолчанию (первый менеджер, указанный в классе). Вы можете повторно использовать менеджер в нескольких моделях, и атрибут будет влиять только на те классы, для которых он был менеджером по умолчанию.

Это то, что я думал, пример поможет понять. Позволяет использовать следующие модели членов и профилей, связанные один-к-одному:

from django.db import models  

class Member(models.Model):
    name = models.CharField(max_length=100)
    active = models.BooleanField()

    def __unicode__(self):
        return self.name


class Profile(models.Model):
    member = models.OneToOneField(Member)
    age = models.PositiveIntegerField()

    def __unicode__(self):
        return str(self.age)

Мы создадим пару членов, активного Джона и неактивного Фила, и настроим профиль для каждого из них:

>>> m1 = Member(name='John', active=True)
>>> m1.save()
>>> p1 = Profile(member=m1, age=18)
>>> p1.save()
>>> m2 = Member(name='Phil', active=False)
>>> m2.save()
>>> p2 = Profile(member=m2, age=35)
>>> p2.save()

Как Django хранит отношения

Сначала давайте посмотрим, как Django хранит отношения. Мы возьмем профиль Джона и посмотрим на его пространство имен:

>>> p = Profile.objects.get(id=1)
>>> p.__dict__
{'age': 18, '_state': <django.db.models.base.ModelState object at 0x95d054c>, 'id': 1, 'member_id': 1}

Здесь мы видим, что отношение определяется сохранением значения идентификатора экземпляра члена. Когда мы ссылаемся на атрибут member, Django использует менеджера для получения сведений о члене из базы данных и создания экземпляра. Кстати, эта информация затем кэшируется на случай, если мы захотим использовать ее снова:

>>> p.member
<Member: John>
>>> p.__dict__
{'age': 18, '_member_cache': <Member: John>, '_state': <django.db.models.base.ModelState object at 0x95d054c>, 'id': 1, 'member_id': 1}

Какой менеджер использовать

Если не указано иное, Django использует стандартный менеджер для поиска этих отношений, а не какой-либо пользовательский менеджер, добавленный в модель. Например, скажем, мы создали следующий менеджер, чтобы возвращать только активных участников:

class ActiveManager(models.Manager):
    def get_query_set(self):
        return super(ActiveManager, self).get_query_set().filter(active=True)

И затем мы добавили его в нашу модель Member в качестве менеджера по умолчанию (в реальной жизни это плохая идея и торговля; в качестве ряда утилит, таких как команда управления dumpdata, используется исключительно менеджер по умолчанию, и, следовательно менеджер по умолчанию, который отфильтровывает экземпляры, может привести к потере данных или аналогичным неприятным побочным эффектам):

class Member(models.Model):
    name = models.CharField(max_length=100)
    active = models.BooleanField()

    objects = ActiveManager()

    def __unicode__(self):
        return self.name

Теперь, когда менеджер отфильтровывает неактивных пользователей, мы можем получить членство Джона только из базы данных:

>>> Member.objects.all()
[<Member: John>]

Однако доступны оба профиля:

>>> Profile.objects.all()
[<Profile: 18>, <Profile: 35>]

И, следовательно, мы можем получить доступ к неактивному члену через профиль, так как для поиска отношений по-прежнему используется стандартный менеджер, а не наш пользовательский:

>>> p = Profile.objects.get(id=2)
>>> p.member
<Member: Phil>

Однако, если мы теперь установим атрибут use_for_related_fields для нашего менеджера, это скажет Django, что он должен использовать этот менеджер для любых поисков отношений:

class ActiveManager(models.Manager):
    use_for_related_fields = True

    def get_query_set(self):
        return super(ActiveManager, self).get_query_set().filter(active=True)

Следовательно, мы больше не можем получить учетную запись Фила из его профиля:

>>> p = Profile.objects.get(id=2)
>>> p.member
---------------------------------------------------------------------------
DoesNotExist                              Traceback (most recent call last)

/home/blair/<ipython console> in <module>()

/usr/lib/pymodules/python2.6/django/db/models/fields/related.pyc in __get__(self, instance, instance_type)
    298             db = router.db_for_read(self.field.rel.to, instance=instance)
    299             if getattr(rel_mgr, 'use_for_related_fields', False):
--> 300                 rel_obj = rel_mgr.using(db).get(**params)
    301             else:
    302                 rel_obj = QuerySet(self.field.rel.to).using(db).get(**params)

/usr/lib/pymodules/python2.6/django/db/models/query.pyc in get(self, *args, **kwargs)
    339         if not num:
    340             raise self.model.DoesNotExist("%s matching query does not exist."
--> 341                     % self.model._meta.object_name)
    342         raise self.model.MultipleObjectsReturned("get() returned more than one %s -- it returned %s! Lookup parameters were %s"
    343                 % (self.model._meta.object_name, num, kwargs))

DoesNotExist: Member matching query does not exist.

Обратите внимание, что это действует только в том случае, если пользовательский менеджер является менеджером по умолчанию для модели (т. Е. Это первый определенный менеджер). Итак, давайте попробуем использовать стандартный менеджер в качестве менеджера по умолчанию и наш собственный менеджер в качестве вторичного менеджера:

class Member(models.Model):
    name = models.CharField(max_length=100)
    active = models.BooleanField()

    objects = models.Manager()
    active_members = ActiveManager()

    def __unicode__(self):
        return self.name

Два менеджера работают, как и ожидалось, глядя прямо на участников:

>>> Member.objects.all()
[<Member: John>, <Member: Phil>]
>>> Member.active_members.all()
[<Member: John>]

И поскольку менеджер по умолчанию может извлекать все объекты, поиск отношений также выполняется успешно:

>>> Profile.objects.get(id=2)
>>> p.member
<Member: Phil>

Реальность

Пройдя так далеко, вы теперь узнаете, почему я выбрал отношение один-к-одному для примеров моделей. Оказывается, что в действительности (и в противоречие с документацией) атрибут use_for_related_fields используется только для отношений один-к-одному. Внешний ключ и отношения многие ко многим игнорируют его. Это билет # 14891 в трекере Django.

Возможно ли иметь одного менеджера модели для одного отношения и другого для другого отношения с той же моделью?

Нет. Хотя в это обсуждение упомянутой выше ошибки в будущем это стало возможным.

2 голосов
/ 20 марта 2013

Короткий ответ: пока эта ошибка не исправлена, 'use-for-related-fields' не работает в django, за исключением отношений один-к-одномутак что не беспокойтесь, если ваш вариант использования - m2m или m2one, или вы будете разочарованы.

...