Как use_for_related_fields работает в Django?

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

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

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

Ответы

Ответ 1

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

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

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

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)

Затем мы добавили его в нашу модель-член как менеджер по умолчанию (в реальной жизни это плохая идея и торговля, поскольку ряд утилит, таких как команда управления 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.

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

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