Django:强制选择相关?

mpe*_*pen 37 django django-orm

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

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

M S*_*lle 46

您可以创建自定义管理器,只需覆盖get_queryset它即可应用于任何地方.例如:

class MyManager(models.Manager):
    def get_queryset(self):
        return super(MyManager, self).get_queryset().select_related('foo', 'bar')
Run Code Online (Sandbox Code Playgroud)

(在Django 1.6之前,它是get_query_set).


Dav*_*ers 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',))
Run Code Online (Sandbox Code Playgroud)

然后,您可以在模型类之间轻松地重新使用管理器.作为一个例子使用的情况下,如果你有这将是适当__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',
    )
Run Code Online (Sandbox Code Playgroud)

python-mock启发的MethodCalls对象试图使API更自然.有些人可能会觉得有点混乱.如果是这样,你可以为__init__那些只接受方法调用信息元组的arg或kwarg提供代码.

  • 注意:`get_query_set` 已[在 Django 1.6 中弃用](https://docs.djangoproject.com/en/1.10/releases/1.6/#get-query-set-and-similar-methods-renamed-to-get-查询集)。现在应该替换为`get_queryset`。 (2认同)