Django from first principles, part 18
MP 113: Restructuring the project.
Note: This is the 18th post in a series about building a full Django project, starting with a single file. This series will be free to everyone, as soon as each post comes out.
In the last post, we gave users the ability to write new posts for their blog. That completes the basic functionality for the site. Now that we're finished implementing the functionality we were after, it's a good time to look at the overall structure of the project, and move some pieces around that aren't currently in the best place.
After this work, the overall project structure will just about match the structure that's generated when you run startproject
followed by startapp
.
The current state of the project
Let's look at the structure of the project as it currently stands. Here's the overall directory listing:
$ tree -L 4 ├── accounts │ ├── templates │ │ └── registration │ │ ├── login.html │ │ └── register.html │ ├── urls.py │ └── views.py ├── blogmaker_lite.py ├── blogs │ ├── admin.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── ... │ ├── models.py │ ├── templates │ │ └── blogs │ │ ├── base.html │ │ └── ... │ ├── urls.py │ └── views.py ├── css │ ├── chota.css │ └── custom.css ├── manage.py └── settings.py
I've left out a few things here, such as the scripts that generate sample data. The main things to notice here are the accounts/ and blogs/ directories. Those correspond to apps in a standard Django project.
The one element here that's not typically seen in a Django project is the blogmaker_lite.py file. Here's what's left in that file:
from django.urls import path, include from django.core.handlers.wsgi import WSGIHandler from django.contrib import admin urlpatterns = [ path("admin/", admin.site.urls), path("accounts/", include("accounts.urls")), path("", include("blogs.urls")), ] application = WSGIHandler()
All that's left are a few imports, the project-wide definition of urlpatterns
, and a call to WSGIHandler()
. These elements are typically found in a folder containing files that relate to the project as a whole, rather than a single file. That way each file can have a single purpose.
Moving project files to a project folder
Let's make a folder called blogmaker_lite/, and move everything that relates to the overall project to that folder. We'll need two new files, urls.py and wsgi.py:
$ mkdir blogmaker_lite $ touch blogmaker_lite/urls.py $ touch blogmaker_lite/wsgi.py
Now let's move the URL-related work from blogmaker_lite.py to the new urls.py file:
from django.urls import path, include from django.contrib import admin urlpatterns = [ path("admin/", admin.site.urls), path("accounts/", include("accounts.urls")), path("", include("blogs.urls")), ]
Now all the project-wide URL configuration work is done in a file that only deals with managing URLs.
Next, let's move the WSGI-related work to wsgi.py:
from django.core.handlers.wsgi import WSGIHandler application = WSGIHandler()
This is a really short file, but it has one purpose, and it can be expanded as the project's complexity continues to grow.
With everything in blogmaker_lite.py moved out of that file, we can now delete it:
$ rm blogmaker_lite.py
Moving settings.py
The configuration work that's done in settings.py applies to the project as a whole, so let's move that to the blogmaker_lite/ directory as well:
$ mv settings.py blogmaker_lite/settings.py
In settings.py, we need to make two changes:
from pathlib import Path ROOT_URLCONF="blogmaker_lite.urls" ... DATABASES={ 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': Path(__file__).parents[1] / 'db.sqlite3', } } ...
The root URL configuration is now implemented in blogmaker_lite/urls.py, so the setting for ROOT_URLCONF
is updated to reflect that. Also, the database file is now one level up from the settings file, so that path needs to be changed as well.
There are several files that need to have the correct path to the settings file, so let's take care of those.
Updating manage.py
The manage.py file needs to know where the project's settings are located:
import os from django.core.management import execute_from_command_line if __name__ == "__main__": os.environ.setdefault( "DJANGO_SETTINGS_MODULE", "blogmaker_lite.settings") execute_from_command_line()
This change tells Django to look for settings.py in the blogmaker_lite/ directory.
Now you should be able to restart the development server, and use the site as you have been.
Generating sample data
If you want to regenerate sample data, you'll have to update the path to the settings file in generate_sample_data.py as well:
... # Load settings. os.environ["DJANGO_SETTINGS_MODULE"] = "blogmaker_lite.settings" django.setup() ...
With the settings path updated, you can regenerate sample data any time you need.
The final state of the project
The project is now reasonably well organized. Most of the project-wide resources are in the blogmaker_lite/ directory, blog-specific resources are in blogs/, and user account-related resources are in accounts/.
Let's compare the structure we arrived at with the structure of a standard Django project. Without adding the actual code for the project, here's the commands you might use to start this project using standard Django management commands:
$ django-admin startproject blogmaker_lite . $ python manage.py startapp blogs $ python manage.py startapp accounts
Here's a side-by-side comparison of how the structure generated by these three commands compares with the structure we ended up with:
Both approaches have ended up with an almost identical structure. Some of our choices along the way were influenced by how Django expects a project to be organized. But overall, this is just a reasonable way to organize all the resources that go into a fully-functioning web app.
If we were to expand this project further, the overall structure wouldn't change a whole lot. There would be more and longer files, but the organization would remain fairly consistent.
Conclusions
If we looked ahead at the final state of this project back when we took the very first steps, it might have seemed unnecessarily complex if you were new to web development. But we had good reasons to create each new bit of complexity as the project evolved.
In a similar way, Django has good reasons for including the level of complexity it does at the start of a new project. The people who've been developing Django over the years have seen what kinds of structures work well, and they've built tools that generate known good structures. If we skip using those tools we often end up creating similar structures, one piece at a time.
When you're working on a new project, go ahead and use the startproject
and startapp
commands. But I hope you do so with an understanding of why specific files and directories are being created for you, and how they relate to a fully-developed project.
In the next post we'll deploy the BlogMaker Lite project to a hosting platform, so it's available to anyone who wants to use it.
Resources
You can find the code files from this post in the django-first-principles GitHub repository. The commits from this post are on the part_18 branch. Commits for this branch start at 9919ad
, with the message Moved code from blogmaker_lite.py to blogmaker_lite/ dir.