If you have been working with Python or Django for a while, you’ve likely heard the acronym DRY: Don't Repeat Yourself.
But as your project grows, you might find yourself copying and pasting the same created_at field across ten models, or the same permission_classes logic across five views. This is where Mixins come to the rescue.
Audience: Beginners in Python and Django aiming to write clean, reusable, and production-ready code.
Takeaway: By the end of this guide, you will clearly understand what mixins are, why they exist, and how to use them correctly in Python, Django, and Django REST Framework (DRF).
📚 Table of Contents
- Why Do We Need Mixins?
- What Is a Mixin? (Conceptual View)
- Mixins vs Inheritance vs Composition
- Mixins in Pure Python
- Mixins in Django
- Mixins in Django REST Framework (DRF)
- Production Guidelines
- Common Mistakes
- Key Takeaways
- Resources
❓ Why Do We Need Mixins?
As applications grow, duplicate logic starts appearing:
- Authentication checks
- Logging
- Permissions
- Common API behaviors
Copy-pasting this logic:
- Violates DRY (Don’t Repeat Yourself)
- Makes changes risky and error-prone
- Leads to bloated base classes
💡 Mixins allow you to reuse behavior without forcing rigid inheritance trees.
🧠 What Is a Mixin? (Conceptual View)
A mixin is a class that:
- Encapsulates one specific behavior
- Is not meant to be instantiated directly
- Is combined with other classes using multiple inheritance
Think of mixins as:
- ❌ Not: “This class is a User”
- ✅ Yes: “This class can log / can authenticate / can soft-delete”
Examples:
LoggingMixinTimestampMixinPermissionMixin
⚖️ Mixins vs Inheritance vs Composition
| Pattern | Relationship | Best Use Case |
|---|---|---|
| Inheritance | is-a | Core identity |
| Composition | has-a | Delegation |
| Mixins | can-do | Shared behavior |
✅ Rule of thumb: Mixins add capabilities, not identity.
🐍 Mixins in Pure Python
Let’s start with a minimal Python example.
Step 1: Define a Mixin
class LoggingMixin:
def log(self, message):
print(f"[LOG] {message}")
✔ No __init__
✔ No assumptions about where it will be used
Step 2: Use the Mixin
class UserService(LoggingMixin):
def create_user(self, username):
self.log(f"Creating user: {username}")
return {"username": username}
Step 3: Run It
service = UserService()
service.create_user("alice")
Output:
[LOG] Creating user: alice
🧩 The mixin cleanly injects logging behavior without complicating the class.
✅ Python Mixin Best Practices
- Keep mixins small and focused
- Avoid state (
self.some_var) when possible - Avoid
__init__unless you fully understand MRO - Always suffix with
Mixin
🌐 Mixins in Django
Django heavily uses mixins in Class-Based Views (CBVs).
Why Django Uses Mixins
Instead of one massive view class, Django splits behavior into:
- Authentication
- Authorization
- Rendering
- Context handling
Each responsibility lives in a mixin.
🛠️ User-Defined Django Mixin
Let’s create a custom access-control mixin.
from django.shortcuts import redirect
class StaffRequiredMixin:
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
return redirect("/login/")
return super().dispatch(request, *args, **kwargs)
Use it in a view:
from django.views.generic import TemplateView
class DashboardView(StaffRequiredMixin, TemplateView):
template_name = "dashboard.html"
🔑 Order matters: mixins must come before the base view.
🧰 Built-in Django Mixins
Django provides many reusable mixins:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
Example:
class ArticleListView(LoginRequiredMixin, ListView):
model = Article
🚀 Mixins in Django REST Framework (DRF)
DRF is designed around mixins.
Why DRF Uses Mixins
REST APIs often need combinations of:
- List
- Create
- Retrieve
- Update
- Delete
DRF provides one mixin per operation.
📦 Built-in DRF CRUD Mixins
from rest_framework import mixins, viewsets
class BookViewSet(
mixins.ListModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet
):
queryset = Book.objects.all()
serializer_class = BookSerializer
✔ GET /books/
✔ POST /books/
🧠 You compose behavior instead of inheriting everything.
✍️ Custom DRF Mixin Example
class RequestLoggingMixin:
def initial(self, request, *args, **kwargs):
print(f"API call: {request.method} {request.path}")
super().initial(request, *args, **kwargs)
Use it:
class OrderViewSet(
RequestLoggingMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet
):
queryset = Order.objects.all()
serializer_class = OrderSerializer
🏗️ Production Guidelines
- Keep mixins stateless
- Document assumptions clearly
- Unit test mixins independently
- Avoid deep inheritance chains
Use this to debug resolution order:
ClassName.mro()
Got it. I’ll do two things clearly and separately:
- Provide a self-contained “Real-World Production Example” section → You can copy-paste this as-is at the end of the blog post.
- Explain the best way to create a GitHub Gist for this example → Practical, dev.to–friendly, and professional.
🧪 Real-World Production Example: Audit Mixins in Django & DRF
This section demonstrates a real, production-grade use case for mixins that is commonly required in professional Django projects.
🎯 Problem Statement
In most real-world applications, we need to:
- Track when a record was created or updated
- Track who created or updated the record
- Apply this logic consistently across many models and APIs
- Avoid copy-pasting the same logic everywhere
Doing this manually in every model or API view quickly becomes error-prone and difficult to maintain.
🧩 Solution Overview
We solve this using mixins at multiple layers:
- Model mixins → reusable across Django apps
- DRF mixins → reusable across API viewsets
This approach is:
- DRY
- Testable
- Scalable
- Widely used in production systems
1️⃣ Model-Level Mixins (Reusable & Abstract)
# core/mixins/models.py
from django.conf import settings
from django.db import models
class TimeStampedMixin(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class UserAuditMixin(models.Model):
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
related_name="%(class)s_created",
on_delete=models.SET_NULL,
)
updated_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
related_name="%(class)s_updated",
on_delete=models.SET_NULL,
)
class Meta:
abstract = True
Why this is production-ready
-
abstract = True→ no extra database tables - Fully reusable across multiple apps
- Keeps audit logic centralized
- No business logic inside models
2️⃣ Using the Model Mixins
# orders/models.py
from django.db import models
from core.mixins.models import TimeStampedMixin, UserAuditMixin
class Order(TimeStampedMixin, UserAuditMixin):
order_number = models.CharField(max_length=50)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.order_number
Now every Order automatically has:
-
created_at,updated_at -
created_by,updated_by
No duplication. No extra code.
3️⃣ DRF Mixin to Auto-Populate Audit Fields
# core/mixins/drf.py
class AuditFieldsMixin:
"""
Automatically sets created_by and updated_by fields
based on the authenticated user.
"""
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
def perform_update(self, serializer):
serializer.save(updated_by=self.request.user)
This mixin hooks into DRF’s lifecycle methods and keeps audit logic out of serializers and views.
4️⃣ Using the DRF Mixin in a ViewSet
# orders/api/views.py
from rest_framework import mixins, viewsets
from core.mixins.drf import AuditFieldsMixin
from orders.models import Order
from orders.api.serializers import OrderSerializer
class OrderViewSet(
AuditFieldsMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
queryset = Order.objects.all()
serializer_class = OrderSerializer
What’s happening here?
- DRF CRUD behavior → provided by DRF mixins
- Audit behavior → injected via
AuditFieldsMixin - No duplicated logic across endpoints
🧠 Code Walkthrough Summary
- Model mixins handle persistence concerns (timestamps, user tracking)
- DRF mixin handles request-specific behavior
- ViewSets simply compose behavior, instead of implementing it
This pattern scales extremely well in large teams and long-lived codebases.
📌 Why This Is a Strong Real-World Example
- Reflects real enterprise Django patterns
- Demonstrates mixins at multiple architectural layers
- Easy to extend (soft delete, logging, permissions)
- Safe and common in production systems
💡 A complete, production-ready example used in this article is available as a GitHub Gist (https://gist.github.com/urwithajit9/baf0ca2910dd2f3db0ec102376cdaad8).
⚠️ Common Mistakes
❌ Treating mixins as base classes
❌ Putting business logic in mixins
❌ Overusing mixins instead of composition
❌ Ignoring Method Resolution Order (MRO)
✅ Key Takeaways
- Mixins enable DRY and modular design
- They add capabilities, not identity
- Django and DRF are built on mixin philosophy
- Mastering mixins is essential for production Django projects
🔗 Resources
- Python Multiple Inheritance: https://docs.python.org/3/tutorial/classes.html
- Django Class-Based Views: https://docs.djangoproject.com/en/stable/topics/class-based-views/
- Django Auth Mixins: https://docs.djangoproject.com/en/stable/topics/auth/default/
- DRF Mixins & ViewSets: https://www.django-rest-framework.org/api-guide/viewsets/
- Effective Python — Brett Slatkin
Happy coding! 🚀
Top comments (0)