Django is powerful web framework, but with power comes responsibility. In this article, we will cover common mistakes that are even seasoned Django developers make, yet most successful Django projects need to deal with these sooner or later.

Reinventing the wheel

Django has a lot of features out of the box and even more in third party packages. Try "Googling" a problem you want to solve before you write something - there’s probably a feature-rich solution that already exists.

You can also use online catalog Django Projects, where the category "apps" (small components used to build projects) has over 3200 projects. Here is a short sample of interesting packages just from the first two pages of the listing:

Haystack modular search for Django

Django-taggit simple tagging for Django

Django-guardian per object permissions for Django

Django-activity-stream generate generic activity streams from the actions on your site. Users can follow any actors' activities for personalized streams.

BONUS TIP: I highly recommend starting a new Django project with cookiecutter-django. It comes with a custom user model, registration via django-allauth, sending emails via Anymail, and many more default settings related to security and deployment.

Monolith application structure

Django is a loosely coupled framework and it doesn’t require you to build web applications in any particular way. Seasoned Django developers, however, have a standard way of doing things.

The fundamental unit of a Django web application is a Django project which is made up of one or more apps. A Django app is a self-contained package that should only do one thing. For example a blog, a membership app or an event calendar.

Django apps can contain Python modules, Django-specific modules (views, URLs, models, forms, etc), static files, database migrations, management commands, unit tests, and more. You should divide your project into small, reusable applications using simple logic.

\ecommerce_project <= This is your Django project \cart <= This is a Django cart app \checkout <= This is a Django checkout app \products <= This is a Django products app manage.py <= Django project management utility

Using such a structure, related functionality will be closer together, allowing you and your team to see the bigger picture of your application more clearly. On top of that, you can export the application into another project and use it again, or even publish it on PyPi as an open-source module.

Writing fat representations and thin models

Django’s architecture can be described as a Model-Template-View (MTV) or Model-View-Template (MVT).

The model is where most of your business logic should go. It is defined in models.py and resides in the application directory. It also includes database queries that pass results to your view and template layers.

Views consist of code that deals with user interaction like form submission by the user and shaping results from the database to fit your template. It is defined in views.py .

If you don’t write your application logic in models and use views, this means you’ve written your code in a model based on the view. This makes the views “fat”, and the model “skinny.” Models should be fat and views should be skinny.

You should also leverage custom managers. For example, a custom manager can provide method with_counts() , which returns a list of all OpinionPoll objects, each with an extra num_responses attribute that is the result of an aggregate query. For more ideas, check the build-in UserManager.

Too many queries per view, or unoptimized queries

Django's ORM is often blamed for making too many queries, or unoptimized queries. But we see that happening with ORMs in other frameworks as well.

The real problem is we are often unaware of the performance problems and their sources. Once you find out what your bottlenecks are, you’ll be able to choose the right approach to address them. There’s a lot you can do from here, but most likely it’ll be:

Fix plain-broken ORM queries (hello prefetching)

Tweaking and optimizing ORM queries

Add caching in the right places

Provision more resources

django-debug-toolbar is an amazing debugging tool. You can use it to track down performance problems in SQL queries, requests, templates, cache, etc. This little package will help you identify the problems quickly. I highly recommend using it.

Redundant model fields

Since queries can't use computed columns and adding a real column is only a "make migrations" away, often developers duplicate fields that represent the same data in different ways.

Pretty soon half of your Vehicles have is_motorcycle == True and wheel_count == 4, and you're not sure which field to trust (hint: neither).

With Django, you can refactor inconsistent properties like this with the @property decorator. However, while the ORM allows you to access columns as properties, the reverse is not true, so you have to manually refactor every query.

Not adding indexes on models

Even seasoned Django developers forget about indexes. Please, do add indexes to your models! On the other hand, do not index everything, as it will slow down inserts, updates, and deletes. As a general rule, you want an index on anything you will use to filter or join on. Analyze your QuerySets to determinate where indexes are needed.

Inconsistent data validation

Django models can be associated with one or more “forms”, which used to create and update model instances. Forms have a lot of default behavior, particularly validation, which is controlled by the properties of the model. In fact, many properties of the model exist only to control the forms' default behavior.

Many Django developers forget the model can be modified not only through its form. They also forget about keeping track of which constraints are where. Non-null? That's on the model. Define choices on a field, explicitly enumerating what values it can have? That's on the form. Uniqueness? On the model. And so on.

This inconsistent validation results in bad user experience: forms, pre-populated with the existing data for an object, that cannot be submitted because the existing data is invalid.

Summary

Django was crafted to help build software as quickly as possible. However, when rushing with adding new features it is easy to forget something or loose the big picture. In this article, we will looked at some common mistakes that are often made by Django developers and ways to avoid them.

This checklist should useful even if you’re a skilled Django developer because mistakes, like not adding indexes on models or inconsistent data validation, aren’t just limited to new developers.

If you liked this article, you should check out Pros and Cons of Django as web framework for Python developers.