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?
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.
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.
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
).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With