Django: 10 Common Anti-Patterns to Avoid 🦄

Django 5 Release
6th November 2023
Grid System in UX design
23rd November 2023
Show all

Django: 10 Common Anti-Patterns to Avoid 🦄

This post aims to address the ten most common anti-patterns in Django, a popular Python framework. Make sure to avoid these pitfalls to ensure the production-readiness of your code. No time to waste, let’s get started 🏃‍♂️

1. Fat Models

Remember, Django’s philosophy is “Fat models, thin views.” However, bloating models with logic is an anti-pattern. Encapsulate the related logic into separate classes or functions.

# Anti-pattern
class Order(models.Model):
    ...
    def total(self):
        return sum(i.price for i in self.items.all())

# Better approach
class Order(models.Model):
    ...
    def total(self):
        return OrderCalculator(self).total()

class OrderCalculator:
    def __init__(self, order):
        self.order = order
    def total(self):
        return sum(i.price for i in self.order.items.all())

2. Using Null=True on String-Based Fields

In Django, empty string values will always be stored as empty strings, not as NULL. Don’t use Null=True for string-based fields like CharField and TextField unless necessary.

# Anti-pattern
name = models.CharField(max_length=100, null=True)

# Better approach
name = models.CharField(max_length=100, default="")

3. Repeating Business Logic in Views and Models

Avoid repeating code — ensure DRY (Don’t Repeat Yourself) principle.

# Anti-pattern
def view(request):
    order = get_object_or_404(Order, id=request.POST['order'])
    order.status = 'shipped'
    order.shipped_date = datetime.datetime.now()
    order.save()

# Better approach
def view(request):
    order = get_object_or_404(Order, id=request.POST['order'])
    order.mark_as_shipped()

4. Direct Database Queries in Templates

Never make direct DB queries in templates; it’s against the MVC pattern and can cause performance issues.

<!-- Anti-pattern -->
{% for item in order.items.all %} 

<!-- Better approach -->
<!-- Query done in view -->
{% for item in items %}

5. Not Using Django’s ORM Capabilities

Django ORM offers powerful features, like the F() expressions, to reduce the number of DB hits and increase efficiency.

# Anti-pattern
product.view_count = product.view_count + 1
product.save()

# Better approach
from django.db.models import F
product.view_count = F('view_count') + 1
product.save(update_fields=['view_count'])

6. Ignoring Django Forms

Avoid handling form logic in views. Django’s forms allow you to encapsulate all form-related logic, including validation.

# Anti-pattern
def register(request):
    if request.method == 'POST':
        if not request.POST.get('username'):
            return HttpResponse("Username is required")

# Better approach
class RegisterForm(forms.Form):
    username = forms.CharField()

def register(request):
    if request.method == 'POST':
        form = RegisterForm(request.POST)
        if form.is_valid():
            ...

7. Not Using Django’s Built-in Decorators

Decorators can make the code cleaner. Use them, particularly for access control.

# Anti-pattern
def edit(request, id):
    if not request.user.is_authenticated:
        return redirect('login')
    ...

# Better approach
from django.contrib.auth.decorators import login_required
@login_required
def edit(request, id):
    ...

8. Hardcoding URLs

Hardcoding URLs can cause problems when changing URL structure. Use Django’s reverse() function or the `{% url % }` template tag.

# Anti-pattern
def view(request):
    return redirect('/home/')

# Better approach
from django.urls import reverse
def view(request):
    return redirect(reverse('home'))
In templates:
<!-- Anti-pattern -->
<a href="/home/">Home</a>

<!-- Better approach -->
<a href="{% url 'home' %}">Home</a>

9. Not Optimizing QuerySets

Avoid N+1 problem by using select_related and prefetch_related.

# Anti-pattern
for order in Order.objects.all():
    print(order.customer.name)  # Each iteration hits the DB

# Better approach
for order in Order.objects.select_related('customer').all():
    print(order.customer.name)  # Hits the DB once

10. Ignoring Django’s Testing Framework

Writing tests is not an option but a requirement. Django’s built-in testing framework is powerful; make the most of it.

# Anti-pattern
# Missing tests!

# Better approach
from django.test import TestCase
from .models import Order
class OrderModelTest(TestCase):
    def test_order_total(self):
        ...

Remember, writing maintainable and efficient Django code is an art that requires constant learning. Keep these anti-patterns in mind to avoid common pitfalls, ensure code quality and enhance the performance of your Django applications.

Fore More Information: xpertlab.com