Django project structure
This post talks about Django applications framework, not Django projects.
I’m writing this post after a while of trying out some of the ideas of structuring applications put forward by Octopus Energy.
What’s the point?
How many times have you created a Django application inside a project and thought it made perfect sense?
In my case I’ve never felt I’ve done it perfectly. I always realise it could be better later down the line. At that point you usually don’t have energy or time to change it. It’s an already working code that does not need to be touched. Not a priority in any meaning of the word. And usually it is not sensible as you will introduce bugs when you do any kind of major refactor. Basically, you need to get your project structure right pretty early on.
I think Django applications are a great way to be able to package an add-on.
But why subject yourself to building a project that contains multiple
applications that all end up being interdependent on each other and will not
ever be used in separation from this project? Most things in your project will
end up depending on each other. You’ll end up with a core
or
utils
app that your project utilises too. I think people are living an illusion of
building independent applications that do not depend on each other. Independent
for what sake? It’s not like one will ever make them into separate packages or
be run independently. Take any project and try to extract a single Django
application out of it. It’s probably impossible.
Most projects I build contain non-reusable applications. I don’t think it makes any sense to divide the project into multiple folders the way that the “best practice” seems to dictate. Things I work on never really have a need to divide projects into artificially segregated applications other than a desire for a ritual and consistency.
The idea of Engines in Rails is much more appealing to me. You only use them when you need them, not as a default.
Some people say that Django applications way is great because “all Django developers” know it. While it’s true, there’s more to programming than Django. Django does not lock you into doing things certain way - you can lay it out however you like. I don’t think it’s a good idea to use the applications framework as a default. I think you can use your common sense to lay out folders. I think before starting on a project one should make an assessment what project structure will work for it, e.g. whether it needs to be horizontal or vertical. I think it really depends on a specific project what kind of folder structure makes sense.
Models
I’d love if you could re-jig the application after you’ve deployed it, but the migration system makes it almost impossible to do later down the line. Once a Django model is added to an app, you really can’t move it without legwork.
Octopus Energy
suggests to do is to just don’t bother with the whole
applications thing. All the models should live in one application only - they
call the application data
. Then you categorise models into separate files
yourself. They’ll always be part of the data
application. I like this because
it means rather than opting into using applications without thinking, opt in
only when you actually need them.
That way you avoid having to manage migrations between multiple applications. You can move your models around into different files later on without having to perform a surgery on your database or manually edit migration files.
data
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
└── models
├── comment.py
├── __init__.py
├── post.py
└── user.py
Save yourself some time by not performing rituals of deciding what model should live in which app. Keep it simple, stupid.
Have you ever asked yourself how do Rails or Laravel developers survive without slicing projects into “re-usable” or “pluggable” modules each time they want to add a new database table?
User interface and data interdependency
It feels so weird to section your project into applications that contain both data and UI logic when you think about it. Let’s say you have four interfaces:
- User interface for the front-end users
- User interface for the back-end users
- Command line commands executed by cron or developers
- API used by a third-party service
Django applications revolve around models. Then you are asked to stick all the GUI & API views, and command line interface into the app that utilise the models used in this app. Have you ever done it perfectly? I never have. It never scratches an itch for me.
What if two different user interfaces that revolve around the same model utilise
forms, would you mix them in the forms.py
file? Would you mix the views for
different kind of interfaces in the views.py
file?
If your view uses models from multiple application and it’s not clear which application it should live in, how do you decide that? Or do you just create yet another application just for those views to live in?
Octopus Energy conventions suggest you break up with the Django applications approach and segregate your interface (views, forms, serialisers, templates) into separate folders.
You end up with something like:
interfaces
├── api
│ ├── __init__.py
│ ├── serializers
│ │ ├── comments.py
│ │ ├── __init__.py
│ │ ├── posts.py
│ │ └── users.py
│ ├── urls
│ │ ├── comments.py
│ │ ├── __init__.py
│ │ ├── posts.py
│ │ └── users.py
│ └── views
│ ├── comments.py
│ ├── __init__.py
│ ├── posts.py
│ └── users.py
├── cli
│ └── management
│ ├── commands
│ │ ├── __init__.py
│ │ └── update_comments_count.py
│ └── __init__.py
├── dashboard
│ ├── forms.py
│ ├── __init__.py
│ ├── urls.py
│ └── views.py
└── website
├── forms.py
├── __init__.py
├── urls.py
└── views.py
Business logic - does it belong on views or models?
Answer is - neither.
Models are data - they are just data. They just store a collection of primitive values and give you the ability to access or mutate them. Usually trying to do more on models will bring chaos to your project.
Views are the user interface. Nothing to do with actually changing the state of model instances or deciding when to send an email to someone, right? Views just deal with the displaying UI and validating user inputs, and triggering the business logic. They should not actually contain that business logic.
Octopus Energy conventions suggest you actually extract your business logic to pure Python functions into something called “application” and “domain” layers.
It’s definitely a problem faced by a lot of developers - do you put X on a model or a view? Do you keep your models or views thin? How do you decide which side of the relationship contains the method to perform a certain operation? How do you test business logic encapsulated in an interface without turning tests into bloat? It does not ever feel right whichever way you do it.
My answer would be to quit this demagogy and create a specific business logic layer that can be called by any type of interface.
Mitchel Cabuloy with whom I work at Torchbox put forward an idea for something called “service objects” which take care of exporting the business logic onto another layer. It’s worth checking out!