Django:强制选择相关?

时间:2011-02-03 22:55:58

标签: django django-orm

我已经创建了一个模型,我正在为它渲染默认/未修改的模型表单。这就产生了64个SQL查询,因为它有很多外键,而这些外键又有更多的外键。

是否可以强制它始终(默认情况下)每次返回其中一个模型时执行select_related

3 个答案:

答案 0 :(得分:45)

您可以创建自定义管理器,只需覆盖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)。

答案 1 :(得分:36)

这也是一个有趣的伎俩:

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__方法,它会生成一个字符串,其中包含来自相关模型的某些信息(或其他任何意味着相关模型几乎总是必填)。

...如果你真的想要变得古怪,这里有一个更通用的版本。它允许您使用argskwargs的任意组合调用默认查询集上的任何方法序列。代码中可能存在一些错误,但您明白了。

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-inspired MethodCalls对象试图使API更自然。有些人可能会觉得有点混乱。如果是这样,你可以为__init__ arg或kwarg提供那些只接受方法调用信息元组的代码。

答案 2 :(得分:2)

创建自定义models.Manager并覆盖所有方法(filterget等)并将select_related附加到每个查询中。然后将此管理器设置为模型上的objects属性。

我建议您只需查看代码并在需要的地方添加select_related,因为在所有内容上执行select_related会导致一些严重的性能问题(并且不会完全清楚它会在哪里发生)从)。