In this tutorial, we will look at some common mistakes that are often made by Django developers and ways to avoid them. This tutorial is useful even if you’re a skilled Django developer because mistakes, like maintaining an unmanageably large settings or naming conflicts in static assets, aren’t just limited to new developers taking their first stab at Django.

Django is a free and open source Python web framework that helpfully solves common development challenges and allows you build flexible, well-structured applications. Django has a lot of modern features out of the box. For me personally, the Admin, Object Relational Mapping tool (ORM), Routing, and Templating features made Django my first choice because applications require a lot of work and, while I enjoy my job as much as any developer could, I want to spend as little time as possible on these basic repetitive tasks. Django allows you to do all this without compromising on flexibility.

Django’s killer feature is a powerful configurable admin interface which builds automatically (automagically?) from your models’ schema and admin panel models, making you feel like a wizard. Through the Admin interface, a user can configure a lot of things including the access control list (ACL), row-level permissions and actions, filters, orders, widgets, forms, extra URL helpers, and anything else you can imagine. I believe every application requires an admin panel—if not yet, it’s simply a matter of time until your basic application needs one. With Django admin, you can create one quickly and flexibly.

Django has a powerful ORM which works with all major databases out of the box. Since it’s lazy, it hits your database only when you need it, unlike other ORMs. It supports all major SQL instructions (and functions) which you can use from your Python source code and feels very comfortable because of Python’s features.

Django’s templating engine is very flexible and powerful at the same time. You can use a lot of standard filters and tags as well as create your new custom filters and tags for your project. Django supports other template engines as well as Django templates, and it provides an API for easy integration of other template engines through standard shortcut functions for templates processing.

Django has a lot of other big features like a URL router that can parse incoming requests and build new URLs from a router schema. As a whole, the Django framework is a pleasant experience and whenever you need help, just read the documentation.

Mistake No. 1: Using the Global System Python Environment for Project Dependencies

Don’t use Python’s global environment for project dependencies, since it can produce dependency conflicts. Python can’t use multiple package versions at the same time. This can be a problem if different projects require different incompatible versions of the same package.

This mistake is usually made by new Python and Django developers that don’t know about Python’s environment isolation features.

There are a lot of ways to isolate your environment, but the most common ways are:

  • virtualenv: A Python package which generates a Python environment folder and has scripts for [de]activating the environment and managing installed Python packages in the environment. This is my favorite method because it is the simplest way to do the job. Usually, I create the environment close to the project folder.
  • virtualenvwrapper: A Python package which installs globally and provides a toolset for creating/deleting/activating/etc. virtual environments. All virtual environments are stored in one folder (which can be overridden through environment variable WORKON_HOME). I don’t see any advantages to using virtualenvwrapper instead of virtualenv.
  • Virtual Machines (VM): There’s no greater isolation than an entire virtual machine dedicated to your application. There are plenty of tools to choose from, including VirtualBox (free), VMware, Parallels, and Proxmox (my personal favorite, and it has a free version). Combined with a VM automation tool like Vagrant, this can be an extremely powerful solution.
  • Containers: In the past few years, I’ve been using Docker in almost every project, especially in every new project that I start from scratch. Docker is an amazing tool that provides a lot of features and has a lot of third-party tools for container automation. It has a layer caching feature which makes rebuilding your containers extremely fast. In containers, I use the global system Python environment, because every container has its own filesystem and projects are isolated on the high level. Docker allows new team members to start work on the project faster, especially if they have Docker experience.

If you ask me, I prefer the virtualenv Python package and Docker containers for project dependency isolation and management.

Mistake No. 2: Not Pinning Project Dependencies in a requirements.txt File

Every new Python project should start with a requirements.txt file and a new isolated environment. Normally you install all packages through pip/easy_install but never forget to add them to your requirements.txt file too. This makes it easier (possible to be more appropriate) to deploy your project on servers, or for a team member to bootstrap the project on their own machine.

Additionally, it is just as important to pin the specific version of your dependencies in your requirements.txt file. Usually, different versions of a package provide different modules, functions, and function parameters; even a minor version change in a dependency can break your package. This is a very serious problem if your project is live and you have regularly scheduled deployments since, without versioning, your build system will always install the latest available version of the package.

Always pin your packages for production! Personally, I use a very nice tool called pip-tools which helps me do this. It provides a set of command-line tools that help manage your dependencies. It automatically generates a requirements.txt that pins not just your dependencies but your entire dependency tree, which includes the dependencies of your dependencies.

Sometimes, you want to update only some packages from your dependencies list (for example, only Django/Flask/any framework or utility), if you used “pip freeze” you don’t know which dependencies are for which packages, and so you can’t upgrade a dependency. With pip-tools, however, it automatically pins the packages depending on which dependency you pinned, so it automatically resolves which packages need to be updated. As a bonus, you also know exactly which package came from which dependency because of how it marks them with comments in the requirements.txt file.

To be extra cautious, it’s a nice idea to back up your dependency source files too! Keep a copy in your file system, a Git-managed folder, S3 folder, FTP, SFTP—wherever, but have it on hand. There have been instances where a relatively minor package being unlisted broke a large number of packages on npm. Pip helpfully provides the tool for downloading all required dependencies as source files, read more by running pip help download.

Mistake No. 3: Using Old-style Python Functions Instead of Class-based Views

Sometimes it is a good idea to use a small Python function in an application’s views.py file especially for tests or utility views, but generally, you should use class-based views (CBVs) in your applications.

CBVs are generic views that provide abstract classes implementing common web development tasks built by professionals and covering all common behaviors. They have an amazing structured API, and you can use all of the advantages of object-oriented programming when you use CBVs. It makes your source code more clear and readable. Forget the pain of using Django standard view functions for listings, CRUD operations, forms processing, etc. You just extend the suitable CBV for your view and override class properties or functions (usually a function returns a property and you can add any logic there what makes spaghetti from your source code in case of using view functions instead of CBVs) which configure the view behavior.

For example, you can have different mix-ins in your project which override basic CBV behaviors for building view contexts, checking authorization on the row level, auto-building template paths from your application structure, integrating smart caching, and more.

I built the package named Django Template Names, which standardizes template names for your views based on an application name and a view class name. I use it every day and it saves a lot of my time for inventing names. Simply put the mixin in your CBV—class Detail(TemplateNames, DetailView):—and it will begin working! Of course, you can override my functions and add mobile responsive templates, different templates for user-agents, or anything else you want.

Mistake No. 4: Writing Fat Views and Skinny Models

Writing your application logic in views instead of models means you’ve written code that belongs in your model into the view, making it “fat” and your model “skinny.”

You should write fat models, skinny views.

Break logic into small methods on your models. This allows you use it multiple times from multiple sources (admin interface UI, front-end UI, API endpoints, multiple views) in a few lines of code instead of copy-pasting tons of code. So next time you’re sending a user an email, extend the model with an email function instead of writing this logic in your controller.

This also makes your code easier to unit test because you can test the email logic in one place, rather than repeatedly in every controller where this takes place.

You can read more about the problem in the Django Best Practices project. The solution is simple: Write fat models and skinny views, so let’s do it in your next project (or refactor your current one).

Mistake No. 5: A Huge, Unmanageable Settings File

Even the new Django project settings file has a lot of settings. In a real project, a settings file grows to 700+ lines of configuration and is going to become difficult to maintain, especially when your dev, production, and staging environments all need custom configurations.

You can divide the configuration file manually and create custom loaders, but I want to introduce you to a nice and well-tested Python package, Django Split Settings, that I have co-authored.

The package provides two functions—optional and include—which support wildcards for the paths and import your configuration files in the same context, making it simple to build your configuration using declared configuration entries in previously loaded files. It doesn’t affect Django performance and you can use it in any project.

Check out the minimal configuration example:

from split_settings.tools import optional, include

include(
    'components/base.py',
    'components/database.py',
    'components/*.py',

    # the project different envs settings
    optional('envs/devel/*.py'),
    optional('envs/production/*.py'),
    optional('envs/staging/*.py'),
    
    # for any local settings
    optional(‘local_settings.py'),
)

Mistake No. 6: All-in-one Application, Bad Application Structure, and Incorrect Resource Placement

Any Django project consists of multiple applications. In Django notation, an application is a Python package which contains at least __init__.py and models.py files; in the latest Django versions, models.py is no longer required. __init__.py is enough.

Django applications can contain Python modules, Django-specific modules (views, URLs, models, admin, forms, template tags, etc), static files, templates, database migrations, management commands, unit tests, and more. You should divide your monolith applications into small, reusable applications using simple logic. You should be able to describe the entire purpose of the app in one or two short sentences. For example: “Allows users to register and activate their account by email.”

It is a good idea is to call the project folder project and place applications in project/apps/. Then, place all application dependencies into their own subfolders.

Examples:

  • Static files: project/apps/appname/static/appname/
  • Template tags: project/apps/appname/templatetags/appname.py
  • Template files: project/apps/appname/templates/appname/

Always prefix the application name in the subfolders because all static folders are merged into one folder and, if two or more applications had a js/core.js file, the last application in settings.INSTALLED_APPLICATIONS will override the previous ones. I once had this bug in my current project and lost about six hours debugging until I realized another developer had overridden static/admin/js/core.js because the team was implementing a custom SPA admin panel and named their files the same way.

Here is example structure for a portal application which has a lot of resources and Python modules.

root@c5b96c395cfb:/test# tree project/apps/portal/
project/apps/portal/
├── __init__.py
├── admin.py
├── apps.py
├── management
│   ├── __init__.py
│   └── commands
│       ├── __init__.py
│       └── update_portal_feeds.py
├── migrations
│   └── __init__.py
├── models.py
├── static
│   └── portal
│       ├── css
│       ├── img
│       └── js
├── templates
│   └── portal
│       └── index.html
├── templatetags
│   ├── __init__.py
│   └── portal.py
├── tests.py
├── urls.py
└── views.py

11 directories, 14 files

Using such a structure, you can at any moment export the application into another Python package and use it again. You can even publish it in PyPi as an open source package, or move it to another folder.

You’ll end up with a project structure like this:

root@c5b96c395cfb:/test# tree -L 3
.
├── deploy
│   ├── chef
│   └── docker
│       ├── devel
│       └── production
├── docs
├── logs
├── manage.py
├── media
├── project
│   ├── __init__.py
│   ├── apps
│   │   ├── auth
│   │   ├── blog
│   │   ├── faq
│   │   ├── pages
│   │   ├── portal
│   │   └── users
│   ├── conf
│   ├── settings.py
│   ├── static
│   ├── templates
│   ├── urls.py
│   └── wsgi.py
└── static
    └── admin
        ├── css
        ├── fonts
        ├── img
        └── js

25 directories, 5 files

In a real project, of course, it will be more complex, but this structure makes things simpler and cleaner.

Mistake No. 7: STATICFILES_DIRS and STATIC_ROOT Confuse Newbie Django Developers

STATICFILES_DIRS? STATIC_ROOT?

Static files are assets which do not change through the app’s use, e.g., JavaScript, CSS, images, fonts, etc. In Django, they are only “collected” into a public directory during the deploy process.

In development mode—python manage.py runserver—Django searches for static files using the STATICFILES_FINDERS setting. By default, it tries to find the requested static file in folders listed in the STATICFILES_DIRS setting. In case of failure, Django tries to find the file using django.contrib.staticfiles.finders.AppDirectoriesFinder, which looks in the static folder of every installed application in the project. This allows you write reusable applications which are shipped with their own static files.

In production, you serve your static using a standalone web server like Nginx. The web server knows nothing about the Django project applications structure or which folders your static files are distributed in. Fortunately, Django provides you with the collect static management command python manage.py collectstatic, which walks through STATICFILES_FINDERS and copies all static files from applications static folders and folders listed in STATICFILES_DIRS into the directory you specify in the STATIC_ROOT setting. This allows for resolution of static file resources using the same logic as Django development mode server and has all static files in one place for your web server.

Don’t forget to run collectstatic in your production environment!

Mistake No. 8: Default STATICFILES_STORAGE, Django Templates Loaders in Production

STATICFILES_STORAGE

Let’s talk about production environment asset management. We can provide the best user experience if we use an “assets never expire” policy (which you can read more about here). It means that all our static files should be cached by web browsers for weeks, months, or even years. In other words, your users should download your assets only once!

That’s cool, and we can do it with few lines in Nginx configuration for our static files folder, but what about cache invalidation? If the user will download our assets only once, what happens if you updated your logo, fonts, JavaScript, or the text color for an item in a menu? To bypass this, you should generate unique URLs and filenames for our static files on every deploy!

We can do it simply by using ManifestStaticFilesStorage as STATICFILES_STORAGE (be careful, hashing is only enabled in DEBUG=false mode) and running the collectstatic management command discussed above. This will decrease assets requests count to your production website and will make your website render much faster.

Cached Django Template Loader

Another cool Django feature is the cached template loader, which doesn’t reload and parse template files on every template render. Template parsing is a very expensive operation and uses a lot of resources. By default, Django templates are parsed on every request, but this is bad, especially during production, where you can process thousands of requests in a short span of time.

Check out the cached.Loader configuration section for a good example and details on how to do this. Don’t use the loader in development mode because it doesn’t reload parsed templates from the file system; you will need to restart your project using python manage.py startapp on every template change. This can be annoying during development, but it is perfect for the production environment.

Mistake No. 9: Pure Python Scripts for Utilities or Scripts

Django provides a very nice feature called Management Commands. Just use it instead of reinventing wheels and writing raw Python scripts for your project utilities.

Also, check out the Django Extensions package, which is a collection of custom extensions for Django. Maybe someone has already implemented your commands! There are already a lot a lot of common task commands.

Mistake No. 10: Reinventing the Wheel

Don't Reinvent the Wheel

Django and Python have thousands of ready-to-use solutions. Try Googling before you write something that is not unique; there’s probably a feature-rich solution that already exists.

Just try to make things simple. Google first! Install, configure, extend, and integrate into your project if you find a good-quality package, and of course, contribute to open source when you have a chance.

To start with, here’s a list of my own public packages for Django:

  • Django Macros URL makes it easy to write (and read) URL patterns in your Django applications by using macros.
  • Django Templates Names is a small mix-in which allows you to easily standardize your CBV template names.
  • django-split-settings lets you organize Django settings into multiple files and directories. Easily override and modify settings. Use wildcards in settings file paths and mark settings files as optional.

Don’t Repeat Yourself (DRY)!

I really like DRY methodology; that’s why I created Django skeleton as a convenience tool which has some really neat features out of the box:

  • Docker images for development/production, managed by docker-compose, which allows you to orchestrate a list of containers easily.
  • Simple Fabric script for production deploy.
  • Configuration for the Django Split Settings package with settings for base and local sources.
  • Webpack integrated into the project - Only the dist folder will be collected by Django on collectstatic command.
  • Configured all basic Django settings and features like cacheable Django templates in production, hashed static files, integrated debug toolbar, logging, etc.

It’s a ready-to-use Django Skeleton for your next project from scratch and will, hopefully, save you a lot of time by bootstrapping your project. Webpack has minimal basic configuration, but it also has SASS installed pre-configured to handle .scss files.

About the author

Alexandr Shurigin, Spain
member since November 7, 2015
Alexandr loves to create, and his favorite way to do it is through web development that fulfills a client’s need or solves a problem. He's passionate about fast, well-written, user-friendly applications. Providing clients with high-quality apps that meet their business needs is his top priority. He excels at solving technical problems using Python, PHP, and JavaScript, and he builds expert applications from scratch. [click to continue...]
Hiring? Meet the Top 10 Freelance Django Developers for Hire in July 2017

Comments

Javier Medrano delgado
Thanks, nice post!!
Harsha Hegde
Enlightening post! Thank you, My score is 8/10!!
Ezechukwu Ikechukwu James
I love this, I just found a new checklist. Thanks for sharing.
Wojciech
For the isolation of environments I use PyENV. I can have many different versions of Python at one time. Ideal solution in conjunction with TOX. Additional plugins what I recommend is: pyenv-implict pyenv-pip-rehash pyenv-virtualenv
rbanffy
On the requirements thing, you can make life easier by shedding the dependencies (that should remain constant anyway unless you update the versions you actually care about) if you use (shameless plug now) pip-chill (https://github.com/rbanffy/pip-chill)
JanBask Training
I have read your blog and i got a very useful and knowledgeable information from your blog. You have done a great job.
Anton Belonovich
Thanks for "Django Split Settings". Worth a try
Diego Vinicius
nice article
Mario R. Castellanos A.
Great article! Thanks.
spookylukey
I really disagree about function based views vs class based views. I've written tons about this before (https://lukeplant.me.uk/blog/posts/djangos-cbvs-were-a-mistake/ ) but your claim that CBVs make your code *easier* to read or reason about is extremely difficult to swallow. Rather, CBVs very often obfuscate your code, especially if you are using Django's base classes. This even resulted in a major security flaw in Django itself - https://www.djangoproject.com/weblog/2016/nov/21/passwordresetconfirmview-security-advisory/ - caused directly by the fact the CBVs obfuscate control flow. In other projects I've often come across cases where use of CBVs have resulted in security vulnerabilities and bloated code e.g. https://gist.github.com/spookylukey/54f248e7cc1dc5f998b1456b4b601a0f Hopefully I don't need to argue that the function version is many times simpler - not only much shorter, but doesn't involve a massive stack of base classes. Usually, the mistake I see is trying to shoe-horn things into CBVs when a FBV would be far better in every way. Of course, there can be advantages in terms of re-use - but you should use CBVs for a *reason* - because you want to re-use some functionality, because using inheritance is a better fit than composing in other ways, and because despite the disadvantages, in this case the advantages are greater. As a default choice, I'd suggest it is a really bad option - your code will be longer and harder to understand. And I think using Django's base classes often gets you into needless complexity and difficulty - you should consider writing your own - https://lukeplant.me.uk/blog/posts/my-approach-to-class-based-views/
Alexandr Shurigin
I have left few comments for you on your gist page https://gist.github.com/spookylukey/54f248e7cc1dc5f998b1456b4b601a0f. About vulnerabilities - if a developer knows nothing about used functions, their parameters and execution flow - he can create a vulnerabilities in FBV or CBV. No difference where create an error in case if you are unfamiliar with framework API or tired (for example). There is only one option to make no errors - code review by multiple team members. No other ways exists in the world to write stable, secure and bugs-free source code. But thank you for your opinion :)
spookylukey
Code review by multiple team members certainly can help to reduce errors - but there is no way to guarantee no errors. If one person doesn't catch an error, how does having two guarantee you will? And in most places you would be lucky if you can get a single review for your code. The reality is you need multiple approaches to help you write correct code. One of them is to use less error prone APIs and methods. The people who created the CBV password reset views patch for Django knew it extremely well, and are both excellent programmers - Claude Peroz is a core developer, as is Tim Graham who reviewed it, who is also the Django Fellow. You couldn't ask for more qualified people. The fact that they still managed to make this mistake (which is probably the worst security vulnerability Django has had in its history) surely has to count as *some* kind of argument or warning about the use of CBVs? That was certainly the overall tone of our discussion about this issue - https://groups.google.com/forum/#!topic/django-developers/HUZySAw43uE As a Django core team member myself, I'm not at all convinced that the CBVs in Django have reduced code to the best logical blocks. Adrian Holovaty, one of the original Django authors, has also said he thinks they are a bad idea - https://www.reddit.com/r/django/comments/1kc3go/cbv_lifecycle_documentation/cbnkwig/ . Personally I think Tom Christie's set of base classes ( http://django-vanilla-views.org/ ) is much better than the one in Django, and where I do use CBVs, I go further and flatten the stack even more - https://lukeplant.me.uk/blog/posts/my-approach-to-class-based-views/
Richard Cochrane
There are some great points here. I disagree with #3 but that is already covered below - what I think is more interesting is an improvement that I have seen in my later dev years on #4 - don't make your views skinny in favour of fat models - make your views and models skinny in favour of services. Using your structure, this could be in a /lib/ folder at a level outside of the apps (if the code is common to many of them) or inside of the apps, but these service modules take out of the models code that is not specifically required for the model. For example, if you have user registration, you could well be affecting a User model, Profile model, Email Address model, etc. Rather than just dumping all of this code in the User model or a User Registration Form (whose primarily responsibility is to validate the registration details), it could be better to put it in a separate module and call that from other places, eg. user_registration_form, api_user_registration_form, special_promotion_registration_form. But again, some great reading and pip-tools sounds awesome!
Alexandr Shurigin
Thanks Richard for your review :) For big projects Django supports (since 1.7 or 1.8 version I guess) models in module folder loader (when you can use folder "models" with __init__.py instead of models.py file) which makes source code cleaner and allows you create good source file structures like "models/user/{__init__.py,registration.py,authorization.py,model.py,emails.py}" and create models as a model with multiple mixins from these folders. This will allow you omit folders like libs and make your source code clear & readable :)
Igor Pomaranskiy
I believe you didn't get the main point of CBVs. :) Of course, if you'll try to push some bad designed views into CBV paradigm, the result is terrible. BUT. If your code fits into CBV — it's a good code. The most obvious case can be seen in Django REST Framework. CBVs are PERFECT solution when you design REST API. But in most cases they are also an indicator of well designed application architecture when you do 'ordinary' web apps (i.e. when your views render HTML).
tony
Using function based views is not a mistake. Go to django documentation and read the first sentence https://docs.djangoproject.com/en/1.11/topics/class-based-views/intro/ "Class-based views provide an alternative way to implement views as Python objects instead of functions. They do not replace function-based views, but have certain differences and advantages when compared to function-based view"
salaun cedric
Writing applications with Django since 10 years, good points but i disagree with n°3. FBV is steel a very good choice and simpler than CBV. FBV is nice !
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant Technologies
About the author
Alexandr Shurigin
Python Developer
Alexandr loves to create, and his favorite way to do it is through web development that fulfills a client’s need or solves a problem. He's passionate about fast, well-written, user-friendly applications. Providing clients with high-quality apps that meet their business needs is his top priority. He excels at solving technical problems using Python, PHP, and JavaScript, and he builds expert applications from scratch.