Reusable, generic mixins for Django class-based views
—
Mixins for optimizing database queries in Django class-based views through select_related, prefetch_related, and dynamic ordering. These mixins help reduce database queries and improve performance in list and detail views.
Automatically apply select_related to reduce database queries for foreign key relationships.
class SelectRelatedMixin:
"""Automatically apply select_related for list of relations"""
select_related = None
def get_queryset(self):
"""Apply select_related with appropriate fields to queryset"""Usage example:
from django.views.generic import ListView
from braces.views import SelectRelatedMixin
class OptimizedPostListView(SelectRelatedMixin, ListView):
model = Post
select_related = ['author', 'category', 'author__profile']
# This will generate:
# Post.objects.select_related('author', 'category', 'author__profile')Performance comparison:
# Without SelectRelatedMixin - N+1 queries
class SlowPostListView(ListView):
model = Post
# Template access to {{ post.author.name }} causes additional query per post
# With SelectRelatedMixin - Single query
class FastPostListView(SelectRelatedMixin, ListView):
model = Post
select_related = ['author', 'category']
# All related data fetched in one queryAutomatically apply prefetch_related for many-to-many and reverse foreign key relationships.
class PrefetchRelatedMixin:
"""Automatically apply prefetch_related for list of relations"""
prefetch_related = None
def get_queryset(self):
"""Apply prefetch_related with appropriate fields to queryset"""Usage example:
from django.views.generic import ListView
from braces.views import PrefetchRelatedMixin
class TaggedPostListView(PrefetchRelatedMixin, ListView):
model = Post
prefetch_related = ['tags', 'comments', 'comments__author']
# This will generate:
# Post.objects.prefetch_related('tags', 'comments', 'comments__author')Complex prefetching with Prefetch objects:
from django.db.models import Prefetch
from django.views.generic import ListView
from braces.views import PrefetchRelatedMixin
class AdvancedPostListView(PrefetchRelatedMixin, ListView):
model = Post
def get_queryset(self):
# Custom prefetch with filtering
active_comments = Prefetch(
'comments',
queryset=Comment.objects.filter(is_active=True).select_related('author')
)
return super().get_queryset().prefetch_related(
active_comments,
'tags',
'author__groups'
)Enable URL-based sorting for list views with validation and defaults.
class OrderableListMixin:
"""Order queryset based on GET parameters"""
orderable_columns = None
orderable_columns_default = None
ordering_default = None
order_by = None
ordering = None
def get_context_data(self, **kwargs):
"""Augment context with order_by and ordering"""
def get_orderable_columns(self):
"""Check that orderable columns are set and return them"""
def get_orderable_columns_default(self):
"""Which column(s) should be sorted by default"""
def get_ordering_default(self):
"""Which direction should things be sorted"""
def get_ordered_queryset(self, queryset=None):
"""Augment QuerySet with order_by statement if possible"""
def get_queryset(self):
"""Returns ordered QuerySet"""Usage example:
from django.views.generic import ListView
from braces.views import OrderableListMixin
class SortablePostListView(OrderableListMixin, ListView):
model = Post
orderable_columns = ['title', 'created', 'author__username', 'category__name']
orderable_columns_default = 'created'
ordering_default = 'desc'
# URLs:
# /posts/ - Default: ordered by created desc
# /posts/?order_by=title&ordering=asc - Title ascending
# /posts/?order_by=author__username - Author username desc (default)Template usage:
<!-- Template: post_list.html -->
<table>
<thead>
<tr>
<th>
<a href="?order_by=title&ordering={% if order_by == 'title' and ordering == 'asc' %}desc{% else %}asc{% endif %}">
Title
{% if order_by == 'title' %}
{% if ordering == 'asc' %}↑{% else %}↓{% endif %}
{% endif %}
</a>
</th>
<th>
<a href="?order_by=created&ordering={% if order_by == 'created' and ordering == 'asc' %}desc{% else %}asc{% endif %}">
Created
{% if order_by == 'created' %}
{% if ordering == 'asc' %}↑{% else %}↓{% endif %}
{% endif %}
</a>
</th>
</tr>
</thead>
<tbody>
{% for post in object_list %}
<tr>
<td>{{ post.title }}</td>
<td>{{ post.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>Combine select_related and prefetch_related for maximum efficiency:
from django.views.generic import ListView
from braces.views import SelectRelatedMixin, PrefetchRelatedMixin
class FullyOptimizedListView(SelectRelatedMixin, PrefetchRelatedMixin, ListView):
model = Post
select_related = ['author', 'category'] # Foreign keys
prefetch_related = ['tags', 'comments'] # Many-to-many and reverse FKsCombine ordering with query optimization:
from django.views.generic import ListView
from braces.views import SelectRelatedMixin, OrderableListMixin
class SortableOptimizedListView(SelectRelatedMixin, OrderableListMixin, ListView):
model = Post
select_related = ['author', 'category']
orderable_columns = ['title', 'created', 'author__username', 'category__name']
orderable_columns_default = 'created'
ordering_default = 'desc'Custom queryset modifications with optimization mixins:
from django.views.generic import ListView
from braces.views import SelectRelatedMixin, PrefetchRelatedMixin
from django.db.models import Count, Prefetch
class AdvancedOptimizedListView(SelectRelatedMixin, PrefetchRelatedMixin, ListView):
model = Post
select_related = ['author', 'category']
def get_queryset(self):
# Get base optimized queryset
queryset = super().get_queryset()
# Add annotations
queryset = queryset.annotate(
comment_count=Count('comments'),
tag_count=Count('tags')
)
# Add custom prefetch
active_comments = Prefetch(
'comments',
queryset=Comment.objects.filter(is_active=True).order_by('-created')
)
return queryset.prefetch_related(active_comments)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Additional context using optimized queries
context['total_posts'] = self.get_queryset().count()
context['categories'] = Category.objects.annotate(
post_count=Count('posts')
).order_by('name')
return contextTrack query performance with debugging:
import logging
from django.views.generic import ListView
from braces.views import SelectRelatedMixin, PrefetchRelatedMixin
from django.db import connection
logger = logging.getLogger(__name__)
class MonitoredOptimizedListView(SelectRelatedMixin, PrefetchRelatedMixin, ListView):
model = Post
select_related = ['author', 'category']
prefetch_related = ['tags']
def get_queryset(self):
initial_queries = len(connection.queries)
queryset = super().get_queryset()
if settings.DEBUG:
query_count = len(connection.queries) - initial_queries
logger.info(f"Queryset generation used {query_count} queries")
return queryset
def get_context_data(self, **kwargs):
initial_queries = len(connection.queries)
context = super().get_context_data(**kwargs)
if settings.DEBUG:
query_count = len(connection.queries) - initial_queries
logger.info(f"Context generation used {query_count} queries")
return contextApply different optimizations based on context:
from django.views.generic import ListView
from braces.views import SelectRelatedMixin, PrefetchRelatedMixin, OrderableListMixin
class ConditionalOptimizedListView(
SelectRelatedMixin,
PrefetchRelatedMixin,
OrderableListMixin,
ListView
):
model = Post
orderable_columns = ['title', 'created', 'author__username']
orderable_columns_default = 'created'
def get_select_related(self):
# Always optimize author
relations = ['author']
# Add category if sorting by it
if self.request.GET.get('order_by', '').startswith('category'):
relations.append('category')
return relations
def get_prefetch_related(self):
relations = []
# Only prefetch tags if user wants to see them
if self.request.GET.get('show_tags'):
relations.append('tags')
# Prefetch comments for staff users
if self.request.user.is_staff:
relations.append('comments')
return relations
def get_queryset(self):
# Set dynamic relations
self.select_related = self.get_select_related()
self.prefetch_related = self.get_prefetch_related()
return super().get_queryset()Install with Tessl CLI
npx tessl i tessl/pypi-django-braces