How to debug in Django

By JoeVu, at: July 21, 2025, 3:28 p.m.

Estimated Reading Time: __READING_TIME__ minutes

How to debug in Django
How to debug in Django

Debugging is an inevitable part of development and doing it effectively can save you hours (or days) of frustration. If you’re working with Django, the good news is that the framework, combined with Python’s tools and community resources, gives you everything you need to debug smartly and efficiently.

 

In this post, we’ll explore the best ways to debug a Django application, from simple techniques to powerful tools used by pros.

 

1. Use pdb - Python’s Built-in Debugger

 

The classic and most recommended way to pause and inspect your code is to insert:

 

Once the interpreter hits that line, it opens an interactive shell where you can:

 

  • View variable values
     

  • Step through code (n for next, s for step into, c to continue)
     

  • Exit debugging (q to quit)

 

Bonus: In Python 3.7+, you can use the cleaner:

 

breakpoint()

 

Which behaves just like pdb.set_trace().

 

2. Add Logging - The Clean Alternative to Print

 

While print() can be useful for quick checks, using Python’s logging module is more flexible and production-friendly.

 

Example setup:

 

import logging
logger = logging.getLogger(__name__)

logger.debug("Debug message")
logger.info("Information")
logger.error("Something went wrong")

 

You can configure log levels and handlers in your settings.py to output logs to console, file, or external services.

 

3. Enable Django’s Built-in Debug Mode

 

Make sure your development environment has: DEBUG = True

 

With this enabled, Django shows detailed error pages with stack traces, local variables, and helpful context.

 

⚠️ Never enable DEBUG in production!

 

4. Install Django Debug Toolbar

 

For a visual and interactive way to debug your Django app, install the Django Debug Toolbar.

 

It displays:

 

  • SQL queries and execution time
     

  • Template context
     

  • Request headers and signals
     

  • Cache usage, static files, and more

 

 

Installation:

 

pip install django-debug-toolbar

 

Add to INSTALLED_APPS and MIDDLEWARE, and include its URLs. Now your browser will show a panel on every page.

 

5. Test Views with Django’s Test Client

 

Sometimes, you want to test a view in isolation.

 

Django’s built-in test client allows you to simulate HTTP requests:

 

from django.test import Client
client = Client()
response = client.get('/your-url/')
print(response.content)

 

Great for debugging APIs and views without opening the browser.

 

6. Use shell_plus from django-extensions

 

Another pro tool is shell_plus, an enhanced interactive shell from the django-extensions package. It auto-imports your models, settings, and lets you test ORM queries or function calls easily.

 

Installation:

 

pip install django-extensions

 

Add it to INSTALLED_APPS and run:

 

python manage.py shell_plus

 

7. Create Custom Debug Middleware

 

Sometimes you want to track down specific issues across requests. Creating your own middleware can help log:

 

  • Incoming requests
     

  • Responses
     

  • Errors

 

A sample middleware:

 

class SimpleLogMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        print(f"Request Path: {request.path}")
        response = self.get_response(request)
        print(f"Response Status: {response.status_code}")
        return response

 

Useful for tracking hard-to-find bugs in production (just be careful with what you log!).

 

Conclusion: Pick the Right Tool for the Right Job

 

Scenario Tool
Need to pause and inspect code pdb / breakpoint()
Want a visual debug interface Django Debug Toolbar
Logging and monitoring logging module
Testing view behavior Django Test Client
Interactive model testing shell_plus
Need custom request logging Middleware

 

 

Pro Tip:

 

“Debugging is like being the detective in a crime movie where you are also the murderer.”
– Filipe Fortes

 

Happy debugging!

 

Questions and Answers

 

What is the best way to pause Django code and inspect variables?

 

You can use Python’s built-in debugger pdb by inserting import pdb; pdb.set_trace() in your code. In Python 3.7+, simply use breakpoint(). This opens an interactive shell where you can step through code, inspect variables, and debug logic interactively.

 

Should I use print() or logging for debugging?

 

While print() is quick, it’s better to use Python’s logging module. It’s more flexible, works well in both development and production, and allows for log level control, formatting, and output to files or monitoring systems.

 

What does Django’s DEBUG mode do?

 

When DEBUG = True in settings.py, Django shows detailed error pages with stack traces, variable values, and helpful debugging context. This is incredibly useful during development but make sure it’s never enabled in production for security reasons.

 

What is Django Debug Toolbar and how does it help?

 

Django Debug Toolbar is a powerful debugging tool that adds a panel to your web pages. It shows SQL queries, execution time, request headers, template context, cache usage, and more helping you inspect what’s happening under the hood of each request.

 

How can I debug views or APIs without using the browser?

 

Use Django’s built-in test client:

 

from django.test import Client
client = Client()
response = client.get('/your-url/')
print(response.content)

 

This allows you to simulate HTTP requests and inspect responses directly in the terminal.

 

What is shell_plus and why should I use it?

 

shell_plus from the django-extensions package is an enhanced interactive shell that auto-imports all your models and settings. It’s perfect for quickly testing ORM queries or trying out functions without manually importing everything.

 

How can I log incoming requests and responses in Django?

 

You can create custom middleware for logging request and response information. For example:

 

class SimpleLogMiddleware:
    def __call__(self, request):
        print(f"Request Path: {request.path}")
        response = self.get_response(request)
        print(f"Response Status: {response.status_code}")
        return response

 

This helps trace issues across requests just avoid logging sensitive info in production.

Tag list:

Related

Subscribe

Subscribe to our newsletter and never miss out lastest news.