DEV Community

David Jiménez
David Jiménez

Posted on

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.

Pre-requisites

Django is a dependency of any third party django package, so you'll have to declare it as such (with poetry, pipenv...)

The solution

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:
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)
Enter fullscreen mode Exit fullscreen mode
  • Add a mock_settings.py too. This file is necessary for the makemigrations 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',
            ],
        }
    },
]
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)