/ development / 5 Django Mistakes I Made (So You Don't Have To)
development 8 min read

5 Django Mistakes I Made (So You Don't Have To)

I wasted weeks on these Django mistakes. Here's what broke, how I fixed it, and what I learned building production apps as a solo developer.

5 Django Mistakes I Made (So You Don't Have To) - Complete development guide and tutorial

I broke my first Django app in production three times before I figured out what I was doing wrong.

Each mistake cost me hours of debugging, angry users, and that sinking feeling when your server logs light up like a Christmas tree. I'm talking about response times jumping from 200ms to 8 seconds. Database connections maxing out. Forms mysteriously failing for half your users.

Here are the five mistakes that hurt the most and what I learned from each one.

Mistake 1.. The N+1 Query Problem That Killed My Database

I built a simple blog with post listings showing author names and comment counts.

Looked perfect in development.

Then I deployed with 500 posts and the homepage took 9 seconds to load. My Postgres connection pool maxed out at 20 simultaneous connections. Users started complaining.

The problem was in my view. I was looping through posts and accessing post.author.name and post.comments.count inside the template. Django hit the database once for the post list, then once per post for the author, then once per post for the comment count.

That's 1 + 500 + 500 = 1,001 queries for a single page load.

What I learned..

Use select related for foreign keys and prefetch related for many-to-many relationships. In my posts view, I changed the queryset to include select related for author and prefetch related with a count annotation for comments. This dropped it to 3 queries total. Page load went from 9 seconds to 180ms.

The fix is simple once you know it exists. But when you're building alone and don't have a senior dev reviewing your code, you ship the N+1 problem straight to production.

How to catch it..

I now run Django Debug Toolbar in development. It shows every query on the page. If I see the same query pattern repeating 50 times, I know I have an N+1 problem before it hits production. I also check my database monitoring dashboard after every deployment. If query count spikes without traffic increasing, something's wrong.

Mistake 2.. Keeping DEBUG=True in Production (Yes, Really)

I shipped my first Django app with DEBUG set to True in production.

For three weeks.

I didn't realize until someone sent me a screenshot of an error page showing my entire settings file including my SECRET KEY, DATABASE PASSWORD, and API keys. Everything was exposed in the traceback.

What I learned..

DEBUG should always be False in production. Always. No exceptions. When DEBUG is True, Django displays detailed error pages with your full stack trace, environment variables, and settings. This is perfect for development. It's a security disaster in production.

I now use environment variables for all configuration. My settings file checks for a DJANGO ENV variable. If it's set to production, DEBUG is False and ALLOWED HOSTS is strictly defined. I also added a deployment checklist that I run through before every push. First item.. verify DEBUG equals False.

The downstream effects I didn't expect..

With DEBUG set to True, Django also serves static files directly, which is incredibly slow. My CSS and JavaScript were loading like I was on dial-up. Turning DEBUG to False and properly configuring static file serving through my reverse proxy cut asset load times by 70 percent.

This mistake taught me that security issues often have performance implications too.

Mistake 3.. Cramming Everything Into Views

My first few Django projects had views that were 200 lines long.

They handled form validation, business logic, database queries, sending emails, calling external APIs, and rendering templates. All in one function. Debugging was a nightmare. Testing was impossible. Adding features meant scrolling through a wall of code trying to figure out where to make changes.

What I learned..

Views should be thin. They receive requests and return responses. That's it. Business logic belongs in model methods, managers, or service modules. I started moving complex logic into model methods. Query logic went into custom managers. External API calls went into a separate services directory. Email sending went into utility functions.

This is the "fat models, skinny views" principle everyone talks about. I didn't get it until I had to add a feature to a 200-line view and spent two hours just understanding what the code was doing.

The real benefit for solo devs..

When your views are simple, you can see the entire request-response flow at a glance. I can now add features in minutes instead of hours because I know exactly where code belongs. Testing is easier because I can test business logic separately from HTTP handling.

My views now average 15 to 25 lines. If a view is longer than that, I know something needs to be extracted.

Mistake 4.. Ignoring Database Indexes Until It Was Too Late

I had a Django app with a Post model that could be filtered by category, tags, author, and publish date.

Worked fine with 100 posts in development.

After six months in production with 50,000 posts, the blog index page started timing out. Category pages took 12 seconds to load. My database CPU usage was constantly at 80 percent.

The problem was I had never added database indexes. Django was doing full table scans on every query. Filtering by category meant scanning all 50,000 rows. Ordering by publish date meant scanning all 50,000 rows and sorting in memory.

What I learned..

Add database indexes on fields you frequently filter, sort, or join on. For my Post model, I added indexes on category, publish date, and author foreign key. I also added a compound index on category and publish date together since that filter combination was common.

After adding indexes, category pages dropped from 12 seconds to 340ms. Database CPU usage dropped to 20 percent. The fix took 10 minutes to implement a migration with db index set to True on the relevant fields.

How I approach indexes now..

I add indexes during initial development on any field I know will be used for filtering or sorting. This includes foreign keys that aren't automatically indexed, date fields, and any fields used in unique together constraints. I also monitor slow query logs on my database. When I see a query taking over 500ms, I check if an index would help.

The tradeoff is that indexes slow down writes slightly and take up disk space. But for a typical blog or SaaS app where reads outnumber writes 100 to 1, the performance gain is absolutely worth it.

Mistake 5.. Not Understanding CSRF Protection

I built a simple contact form for my portfolio site.

It worked perfectly on localhost. Deployed it to production and suddenly every form submission returned a 403 Forbidden error. No explanation. Just "CSRF verification failed."

I spent four hours debugging before I realized the problem. I had set CSRF COOKIE SECURE to True in my settings, but I was testing on a development domain that wasn't using HTTPS. The CSRF token cookie couldn't be set, so Django rejected every POST request.

What I learned..

CSRF protection in Django requires the CSRF token to be included in every POST form. This happens automatically if you use the csrf token template tag. But there are several settings that can break it CSRF COOKIE SECURE must match your HTTPS setup, CSRF TRUSTED ORIGINS needs to include your domain if you're posting from a different origin, and SESSION COOKIE SECURE should be True in production.

The fix was understanding that CSRF COOKIE SECURE should only be True when your site actually runs on HTTPS. In local development, it should be False. I moved all these security settings into environment-specific configuration files. Development has them off, production has them on with proper domain configuration.

The API gotcha I ran into later..

When I added an API endpoint that accepted POST requests from a mobile app, I had to exempt that view from CSRF protection using the csrf exempt decorator. CSRF tokens are for browser-based forms, not API calls. For APIs, I use token authentication instead.

This mistake taught me that Django's security defaults are excellent, but you need to understand what they do and how they interact with your deployment setup. Don't just copy settings from Stack Overflow without knowing why they're there.

What You Should Do Next

Here's what actually matters..

Run Django Debug Toolbar in development to catch N+1 queries before they hit production. Look for repeated query patterns and fix them with select related or prefetch related.

Create a deployment checklist that includes verifying DEBUG is False, checking security settings match your environment, and confirming static files are served correctly.

Keep views under 30 lines by moving business logic to models, managers, or service modules. If you can't understand a view in 10 seconds, it's too complex.

Start here..

Install Django Debug Toolbar today. Run it on your current project. If you see any query executing more than 10 times on a single page, you have an N+1 problem. Fix it now before it becomes a production incident.

The mistakes I made cost me weeks of debugging time. You don't have to repeat them.