DEV Community

Cover image for Understanding Mixins in Python, Django, and Django REST Framework
Ajit Kumar
Ajit Kumar

Posted on

Understanding Mixins in Python, Django, and Django REST Framework

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?

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:

  • LoggingMixin
  • TimestampMixin
  • PermissionMixin

⚖️ 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}")
Enter fullscreen mode Exit fullscreen mode

✔ 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}
Enter fullscreen mode Exit fullscreen mode

Step 3: Run It

service = UserService()
service.create_user("alice")
Enter fullscreen mode Exit fullscreen mode

Output:

[LOG] Creating user: alice
Enter fullscreen mode Exit fullscreen mode

🧩 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)
Enter fullscreen mode Exit fullscreen mode

Use it in a view:

from django.views.generic import TemplateView

class DashboardView(StaffRequiredMixin, TemplateView):
    template_name = "dashboard.html"
Enter fullscreen mode Exit fullscreen mode

🔑 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
Enter fullscreen mode Exit fullscreen mode

Example:

class ArticleListView(LoginRequiredMixin, ListView):
    model = Article
Enter fullscreen mode Exit fullscreen mode

🚀 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Use it:

class OrderViewSet(
    RequestLoggingMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
Enter fullscreen mode Exit fullscreen mode

🏗️ Production Guidelines

  • Keep mixins stateless
  • Document assumptions clearly
  • Unit test mixins independently
  • Avoid deep inheritance chains

Use this to debug resolution order:

ClassName.mro()
Enter fullscreen mode Exit fullscreen mode

Got it. I’ll do two things clearly and separately:

  1. Provide a self-contained “Real-World Production Example” section → You can copy-paste this as-is at the end of the blog post.
  2. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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


Happy coding! 🚀

Top comments (0)