How to Consolidate Multiple Django Projects

Dos and Don’ts For Success

If you’ve been developing web applications for your company or a client for a few years, it’s possible you now find yourself with several individual Django projects that you’d like to consolidate. Each project might have one or a few apps with its own set of URL patterns, models, views, and more. This might be dizzying to think about, but with a few tips you can be well on your way to the lean, well-organized project you always dreamed of.

How not to do it

General Approach

The idea here will be to move your other projects’ applications into the consolidated project. This will be followed by merging and reducing project-level files, like settings.py and requirements.txt, while maintaining any application-level separation you want with modules like your views.py and models.py. This will “trim the fat” so there’s no duplicated code from the parent projects while making sure the resulting project is still manageably organized. Let’s get started!

Moving an Application

The first step for these migrations will be to move an existing application into your consolidated project. Assuming you have a Django project with an app called polls, you might have a directory structure that looks like the following:

my_standalone_project/
manage.py
requirements.txt
my_standalone_project/
settings.py
urls.py
wsgi.py
polls/
models.py
urls.py
views.py
migrations/
...
templates/
polls/
poll_detail.html
...

Everything under the polls/ directory is what you’ll move into the consolidated project. You can drag the folder into the project in your favorite GUI or IDE, or run something like:

$ mv my_standalone_project/polls/ /path/to/consolidated/project/

With the goal being that you move the entire polls/ folder into the project and not just its contents.

Now that you’ve got the bulk of the application moved, the next step is to merge the supporting infrastructure from the parent project into the consolidated one.

settings.py

Each Django project ultimately needs just a single settings module. Coming from n projects you’ll have n of these modules to contend with, so you’ll need to start by merging these. Depending on the complexity of your projects’ setups, this may be the most time-consuming piece. The goal here will be to end with a single module that contains all the settings from the parent applications’ settings modules.

In many cases, settings will simply be duplicated across projects and can be pared down. Settings like USE_I18N or TIME_ZONE are typically identical. Simply mark these off in your parent projects and feel comfortable knowing they’re taken care of.

More involved settings like DATABASES and TEMPLATES will need to be examined carefully. If you have multiple database instances you may need to re-instrument some of your code as outlined in Multiple Databases. You may want to consider migrating your data into a single database instance to avoid this altogether. With TEMPLATES, you’ll want to make sure all template directories are accounted for from the parent projects. You’ll also want to verify that your templates are properly namespaced to the app they’re located in so that there aren’t any confusing name collisions when you try to {% include %} them down the road!

The other settings to look out for will be any that third-party applications such as django-debug-toolbar depend on. Follow the same approach as above, copying and pasting any settings that are novel to a particular application into the project’s settings and merging or resolving any duplicated settings.

The final step is to make sure that your newly-migrated application is listed in the project’s INSTALLED_APPS setting. This is one I always overlook that isn’t always well-described in the resulting error!

requirements.txt

The requirements for each of your original projects might be vastly different if the applications do vastly different things. One might have dependencies for managing image uploads, while another might have dependencies for talking to third-party APIs. In general this will work like merging your settings—you’ll need to contend with conflicting dependency versions by making sure all applications work on a single one of the specified versions. Now might also be a good time to make sure your dependencies are up-to-date!

urls.py

In order to maintain existing URL pattern matching, you’ll need to add your application to the top-level urls.py so the project knows about its patterns. I’ve seen some approaches that result in new URLs because of the way the patterns are consolidated, but this is not necessary. It’s easy enough to maintain existing patterns by doing the following:

import polls
url_patterns = [
...
url('^', include(polls.urls, namespace='polls')),
]

If you didn’t have a namespace on your application in the old project, now is a great time to add one per the code sample above. If you are adding a namespace now, you’ll need to update any references to named URL patterns in reverse calls and {% url %} blocks. Namespacing is a great way to avoid name collisions, especially in large projects where two applications might have need for similarly-named views.

Things to Scrap

If you’ve made it this far, you may just be at a point where you can scrap what remains of the original project. Your remaining files should look something like this:

my_standalone_project/
manage.py
requirements.txt
my_standalone_project/
settings.py
urls.py
wsgi.py

We’ve already handled requirements.txt, settings.py, and urls.py. The remaining files are manage.py and wsgi.py. Unless you’ve created custom management commands or added custom instrumentation into your wsgi.py, it’s likely these modules require no further work on your end. It’s time to say a fond goodbye to your old project and congratulate it on a job well done.

The Icing on the Cake

Now that everything is playing happily together in a single project, there are a few niceties you can take advantage of.

Named URLs

If your old projects were supporting a single site before, chances are these projects had to link to pages served by each other at some point. Since Django only knows about URL patterns within a single project, these URLs were likely duplicated in each project, or worse, in each template where they were used.

Now that all applications are running under a single project, these hard-coded URL patterns can be replaced with their named equivalents! Those ten references to /polls/38 you added in a hurry to release can all be simplified to {% url ‘polls:poll_detail’ 38 %} so that when you move your polls to a new URL pattern down the line you don’t need to make a change in all those places.

Signals

Your models can now send signals that can be acted on by your newly-consolidated applications. This means when a model in the polls app is edited, you can ingest those changes by handling the signal instead of running a manual or periodic process to do so. This is pretty nifty if you rely on behavior and structure of one model to affect another!

These niceties along with the decreased overhead of consolidated Django projects can immensely improve development and bug hunting workflows because the surrounding infrastructure is reduced while the separation of concerns remains. If you have multiple Django applications supporting a single site, consider consolidating them today!