Django: выбор силы связан?
Я создал модель, и я представляю для нее стандартную/немодифицированную модель. Это само по себе генерирует 64 SQL-запроса, потому что у него довольно много внешних ключей, а у них больше внешних ключей.
Можно ли принудительно заставить его (по умолчанию) выполнять select_related
каждый раз, когда возвращается одна из этих моделей?
Ответы
Ответ 1
Вы можете создать пользовательский менеджер и просто переопределить get_queryset
, чтобы он применялся повсеместно. Например:
class MyManager(models.Manager):
def get_queryset(self):
return super(MyManager, self).get_queryset().select_related('foo', 'bar')
(До Django 1.6 это было get_query_set
).
Ответ 2
Вот и забавный трюк:
class DefaultSelectOrPrefetchManager(models.Manager):
def __init__(self, *args, **kwargs):
self._select_related = kwargs.pop('select_related', None)
self._prefetch_related = kwargs.pop('prefetch_related', None)
super(DefaultSelectOrPrefetchManager, self).__init__(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
qs = super(DefaultSelectOrPrefetchManager, self).get_queryset(*args, **kwargs)
if self._select_related:
qs = qs.select_related(*self._select_related)
if self._prefetch_related:
qs = qs.prefetch_related(*self._prefetch_related)
return qs
class Sandwich(models.Model):
bread = models.ForeignKey(Bread)
extras = models.ManyToManyField(Extra)
# ...
objects = DefaultSelectOrPrefetchManager(select_related=('bread',), prefetch_related=('extras',))
Затем вы можете легко использовать менеджера легко между классами модели. В качестве примера использования примера это было бы уместно, если бы у вас был метод __unicode__
для модели, который отображал строку, которая включала некоторую информацию из связанной модели (или что-то еще, что означало, что соответствующая модель почти всегда требовалась).
... и если вы действительно хотите получить дурацкий, здесь более обобщенная версия. Это позволяет вам вызывать любую последовательность методов в наборе запросов по умолчанию с любой комбинацией args
или kwargs
. В коде могут быть некоторые ошибки, но вы получаете эту идею.
from django.db import models
class MethodCalls(object):
"""
A mock object which logs chained method calls.
"""
def __init__(self):
self._calls = []
def __getattr__(self, name):
c = Call(self, name)
self._calls.append(c)
return c
def __iter__(self):
for c in self._calls:
yield tuple(c)
class Call(object):
"""
Used by `MethodCalls` objects internally to represent chained method calls.
"""
def __init__(self, calls_obj, method_name):
self._calls = calls_obj
self.method_name = method_name
def __call__(self, *method_args, **method_kwargs):
self.method_args = method_args
self.method_kwargs = method_kwargs
return self._calls
def __iter__(self):
yield self.method_name
yield self.method_args
yield self.method_kwargs
class DefaultQuerysetMethodCallsManager(models.Manager):
"""
A model manager class which allows specification of a sequence of
method calls to be applied by default to base querysets.
`DefaultQuerysetMethodCallsManager` instances expose a property
`default_queryset_method_calls` to which chained method calls can be
applied to indicate which methods should be called on base querysets.
"""
def __init__(self, *args, **kwargs):
self.default_queryset_method_calls = MethodCalls()
super(DefaultQuerysetMethodCallsManager, self).__init__(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
qs = super(DefaultQuerysetMethodCallsManager, self).get_queryset(*args, **kwargs)
for method_name, method_args, method_kwargs in self.default_queryset_method_calls:
qs = getattr(qs, method_name)(*method_args, **method_kwargs)
return qs
class Sandwich(models.Model):
bread = models.ForeignKey(Bread)
extras = models.ManyToManyField(Extra)
# Other field definitions...
objects = DefaultQuerysetMethodCallsManager()
objects.default_queryset_method_calls.filter(
bread__type='wheat',
).select_related(
'bread',
).prefetch_related(
'extras',
)
Объект MethodCalls
, основанный на python-mock, является попыткой сделать API более естественным. Некоторые могут обнаружить, что это немного запутывает. Если это так, вы можете добавить этот код для __init__
arg или kwarg, который просто принимает кортеж информации о вызове метода.
Ответ 3
Создайте пользовательский models.Manager
и переопределите все методы (filter
, get
и т.д.) и добавьте select_related для каждого запроса, Затем установите этот менеджер как атрибут objects
в модели.
Я бы порекомендовал вам просто пропустить ваш код и добавить select_related
там, где это необходимо, потому что выполнение select_related на всех приведет к серьезным проблемам с производительностью в строке (и это будет не совсем понятно, откуда оно взялось).