我已经创建了一个模型,并且正在为其渲染默认/未修改的模型表单。仅此一项就生成了 64 个 SQL 查询,因为它有相当多的外键,而这些查询又具有更多的外键。
是否可以强制它always(默认情况下)每次返回这些模型之一时执行
select_related
?
您可以创建自定义管理器,只需覆盖
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
)。
这还有一个有趣的技巧:
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__
方法,该方法呈现一个包含来自相关模型的一些信息的字符串(或者任何其他意味着几乎需要 always 需要相关模型)的字符串,那么这将是合适的。
...如果您真的想要变得古怪,这里有一个更通用的版本。它允许您使用
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',
)
受 python-mock 启发的
MethodCalls
对象是使 API 更加自然的尝试。有些人可能会觉得这有点令人困惑。如果是这样,您可以将该代码替换为仅接受方法调用信息元组的 __init__
arg 或 kwarg。
models.Manager
并覆盖所有方法(filter
、get
等)并将 select_lated 附加到每个查询上。然后将此管理器设置为模型上的 objects
属性。
我建议只检查你的代码并在需要的地方添加
select_related
,因为对所有内容执行 select_lated 都会导致一些严重的性能问题(并且不完全清楚它来自哪里)。