Rohan Yeole - HomepageRohan Yeole

Design Principles in Python/Django

By Rohan Yeole

DRY (Don’t Repeat Yourself)

Avoid code duplication by abstracting recurring logic into reusable components.

Django naturally encourages DRY with tools like the ORM, forms, and admin. Example: Using Django REST Framework (DRF) serializers for model validation:

from rest_framework import serializers
from .models import Expense, Category, Balance

class ExpenseSerializer(serializers.ModelSerializer):
class Meta:
model = Expense
fields = ['id', 'user', 'category', 'amount', 'description', 'date']

class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'description', 'user']

class BalanceSerializer(serializers.ModelSerializer):
class Meta:
model = Balance
fields = ['id', 'total_balance']

KISS (Keep It Simple, Stupid)

Favor simplicity over cleverness. Readable code is better than smart code.

Django’s generic class-based views (CBVs) let you write concise and standardized logic for common CRUD operations:

from rest_framework import generics
from .models import Expense
from .serializers import ExpenseSerializer

class ExpenseListView(generics.ListCreateAPIView):
queryset = Expense.objects.all()
serializer_class = ExpenseSerializer
Repeat for Category and Balance with minimal effort.

YAGNI (You Ain’t Gonna Need It)

Don’t implement features until they’re actually needed.

Start small. Add complexity only when justified by real-world needs.

from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField(unique=True)

def __str__(self):
return self.username
No custom managers or over-engineered profile logic — just what you need.

Separation of Concerns (SoC)

Assign distinct responsibilities to different parts of your application.

Django’s MTV (Model-Template-View) architecture supports SoC by design.
Example: Offloading business logic to models/services instead of views:
# models.py
class User(AbstractUser):
def get_total_expenses(self):
return self.expense_set.aggregate(Sum('amount'))['amount__sum'] or 0
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from .models import User

class UserTotalExpensesView(APIView):
def get(self, request, user_id):
user = get_object_or_404(User, id=user_id)
total_expenses = user.get_total_expenses()
return Response({'total_expenses': total_expenses})

SOLID Principles

A set of five principles that promote robust, scalable object-oriented design.

S - Single Responsibility Principle (SRP)

Each class should do one thing only.

class Category(AbstractModel):
name = models.CharField(max_length=100, unique=True)
description = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
O - Open/Closed Principle (OCP)

Code should be open for extension but closed for modification.

class BaseMiddleware:
def process_request(self, request):
raise NotImplementedError

class AuthMiddleware(BaseMiddleware):
def process_request(self, request):
if not request.user.is_authenticated:
return HttpResponse('Unauthorized', status=401)
You can add new middleware by subclassing without changing existing code.

L - Liskov Substitution Principle (LSP)

Subtypes should be replaceable for their parent types without breaking the app.

class Notification:
def send(self):
raise NotImplementedError

class EmailNotification(Notification):
def send(self):
print("Sending email")

class SMSNotification(Notification):
def send(self):
print("Sending SMS")

def notify(notification: Notification):
notification.send()
I - Interface Segregation Principle (ISP)

Don't force classes to implement unused methods.

class CreateMixin:
def create(self, request, *args, **kwargs):
return JsonResponse({'message': 'Create not implemented'}, status=405)

class ReadMixin:
def read(self, request, *args, **kwargs):
return JsonResponse({'message': 'Read not implemented'}, status=405)

class MyView(CreateMixin, ReadMixin, View):
def read(self, request, *args, **kwargs):
return JsonResponse({'message': 'Reading data'})

Split responsibilities using mixins to keep classes focused.

D - Dependency Inversion Principle (DIP)

Depend on abstractions, not concrete implementations.

# services.py
class PaymentService:
def process_payment(self):
raise NotImplementedError

class StripePaymentService(PaymentService):
def process_payment(self):
print("Processing Stripe payment")

# views.py
class PaymentView(View):
def __init__(self, payment_service: PaymentService):
self.payment_service = payment_service

def post(self, request, *args, **kwargs):
self.payment_service.process_payment()
return JsonResponse({'message': 'Payment successful'})
Conclusion

By applying these principles:

  • Your Django code stays modular and easier to test.
  • Maintenance becomes smoother as features grow.
  • Teams can onboard faster and contribute with confidence.

Stick to the basics, avoid premature optimization, and let Django’s clean design do the heavy lifting.