17
How to set up and run migrations in third-party Django packages
Creating a simple python package is a quite easy endeavor: create an empty project, create a module, put content inside, package it and done, you have a fully fledged package. However, creating a package that needs to integrate with Django is not that easy; you need to make sure that the models, migrations, default settings, etc... are in the correct place in order to flawlessly integrate without much pain.
This article will show you a way to run the Django migrations for your third party packages, decoupled from any external dependency. If any of you knows a simpler solution than the one exposed here, I will appreciate it so much!
DISCLAIMER: This guide assumes that some concepts and terms related to Django like migrations and settings are known by the reader.
Django is a dependency of any third party django package, so you'll have to declare it as such (with poetry, pipenv...)
You placed your Django Models in a models.py
, but since you're implementing this third party library outside of the context of your monolith, there is no manage.py
to be found. But you want to run your migrations!
Then you'll need:
- Create a python package folder called
migrations
inside your django package. Once you successfully run the migrations, they will be placed here. - Create a
scripts
folder on the root of the project.- Inside this folder, create a
makemigrations.py
file. Put this content inside it. With that you'll be able to run the makemigrations script:
- Inside this folder, create a
import os
import sys
if __name__ == "__main__":
os.environ.setdefault(
"DJANGO_SETTINGS_MODULE", "scripts.mock_settings"
)
from django.core.management import execute_from_command_line
args = sys.argv + ["makemigrations"]
execute_from_command_line(args)
- Add a
mock_settings.py
too. This file is necessary for themakemigrations
command since any Django execution (and makemigrations is) requires a settings file. However, since we only need it to generate the migrations we can have a sample and dumb settings file just to do this operation. Put this content in the file:
import os
SECRET_KEY = 'Not a secret key at all, actually'
DEBUG = True
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "postgres",
"USER": "postgres",
"PASSWORD": "example",
"HOST": "localhost",
"PORT": "60000",
}
}
INSTALLED_APPS = [
# Django apps necessary to run the admin site
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.staticfiles',
'django.contrib.messages',
# And this app
'your_django_lib',
]
STATIC_URL = '/static/'
MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
}
},
]
NOTE: Change this mock settings depending on your necessities. Instead of having to configure a real postgres database you could just change de DATABASE
settings and configure a sqlite database. However sqlite does not support the likes of the JSONField, and migrations with JSONFields won't work.
- If you need to spin up a real database just for the sake of migrations, you could use docker-compose. Use the following minimal setup:
version: '3.6'
services:
postgres:
image: postgres:9.5-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- 60000:5432
expose:
- "5432"
restart: always
environment:
POSTGRES_PASSWORD: example
volumes:
postgres_data:
In the end, the minimal structure for this to work would look something like this:
your-django-lib/
├─ your_django_lib/
│ ├─ migrations/
│ │ ├─ __init__.py
│ ├─ __init__.py
│ ├─ models.py
├─ scripts/
│ ├─ __init__.py
│ ├─ mock_settings.py
│ ├─ makemigrations.py
├─ .gitignore
├─ docker-compose.yml
Now you can run the makemigrations
script. Depending on your virtual environment you'll have to execute the command in a different manner. With poetry for example you'd run it like this:
poetry run python scripts/makemigrations.py
Congratulations! It's been tough, but now you can run your migrations decoupled from any infrastructure or external Django dependencies! Now you should have your model migrations in the migrations folder!
17