Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: Force select related?

I've created a model, and I'm rendering the default/unmodified model form for it. This alone generates 64 SQL queries because it has quite a few foreign keys, and those in turn have more foreign keys.

Is it possible to force it to always (by default) perform a select_related every time one of these models are returned?

like image 919
mpen Avatar asked Feb 03 '11 22:02

mpen


People also ask

What is select related and prefetch related in Django?

In Django, select_related and prefetch_related are designed to stop the deluge of database queries that are caused by accessing related objects. In this article, we will see how it reduces the number of queries and make the program much faster.

How does select related work?

We use select_related when the object that you're going to select is a single object, which means forward ForeignKey, OneToOne and backward OneToOne . select_related works by creating an SQL join and including the fields of the related object in the SELECT statement.


2 Answers

You can create a custom manager, and simply override get_queryset for it to apply everywhere. For example:

class MyManager(models.Manager):     def get_queryset(self):         return super(MyManager, self).get_queryset().select_related('foo', 'bar') 

(Prior to Django 1.6, it was get_query_set).

like image 180
M Somerville Avatar answered Sep 28 '22 05:09

M Somerville


Here's also a fun trick:

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',)) 

Then you can re-use the manager easily between model classes. As an example use case, this would be appropriate if you had a __unicode__ method on the model which rendered a string that included some information from a related model (or anything else that meant a related model was almost always required).

...and if you really want to get wacky, here's a more generalized version. It allows you to call any sequence of methods on the default queryset with any combination of args or kwargs. There might be some errors in the code, but you get the idea.

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',     ) 

The python-mock-inspired MethodCalls object is an attempt at making the API more natural. Some might find that a bit confusing. If so, you could sub out that code for an __init__ arg or kwarg that just accepts a tuple of method call information.

like image 23
David Sanders Avatar answered Sep 28 '22 06:09

David Sanders