2011-02-03 2 views
30

Я создал модель, и я представляю для нее стандартную/немодифицированную форму модели. Это само по себе генерирует 64 SQL-запроса, потому что у него довольно много внешних ключей, а у них больше внешних ключей.Django: выбор силы связан?

Можно ли заставлять его всегда (по умолчанию) выполнять select_related каждый раз, когда возвращаются одна из этих моделей?

ответ

41

Вы можете создать пользовательский менеджер и просто переопределить 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).

+0

Как это сделать при использовании models.QuerySet? –

+0

Я думаю, вам нужно предоставить дополнительную информацию или какой-нибудь пример кода, что вы имеете в виду; если у вас есть QuerySet, вы можете напрямую называть 'select_related' на нем. –

+0

Я имел в виду, если вы используете объекты = MyQuerySet.as_manager() –

2

Создайте пользовательский models.Manager и переопределите все методы (filter, get и т. Д.) И добавьте select_related для каждого запроса. Затем установите этот менеджер как атрибут objects на модели.

Я бы порекомендовал вам просто пропустить ваш код и при необходимости добавить select_related, потому что выполнение select_related на всех вызовет серьезные проблемы с производительностью по линии (и это будет не совсем понятно, откуда оно взялось).

+0

Затем я должен переопределить все поля формы в форме модели, чтобы я мог установить набор запросов вручную ... – mpen

+0

Ну, вы всегда можете пойти с моим первым предложением. Я просто предупреждал вас о возможных негативных проблемах с производительностью, с которыми вы можете столкнуться. Есть способы сделать это только для модели ModelForm без переопределения всего, но ответ будет зависеть от того, что именно вам нужно сделать. Если вам нужна помощь, просто создайте еще один вопрос с более подробной информацией. – sdolan

+0

Ну, у меня есть модель адреса, которая имеет ссылки на почтовый индекс, город, провинцию и страну. Это почти никогда не будет отображаться без этих полей, поэтому я решил, что могу включить его по умолчанию. – mpen

28

Вот и забавный трюк:

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 объектом является попыткой сделать API более естественным. Некоторые могут обнаружить, что это немного запутывает. Если это так, вы можете добавить этот код для аргумента __init__ arg или kwarg, который просто принимает кортеж информации о вызове метода.

+0

Это действительно спасло мой день - вместо того, чтобы писать тонны 'ModelForm', переопределяя 'ModelChoiceField', пока корова не вернутся домой. (Espacially, если 'MyModel .__ unicode __()' использует то, что должно быть 'select_related'. –

+0

Отличный ответ. Это решает большинство моих проблем, связанных с n + 1. – Eldamir

+1

Примечание:' 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-queryset). Теперь он должен быть заменен на 'get_queryset'. – keithb

Смежные вопросы