Welcome | Get started | Dive into Lino | Contribute | Reference | More

Django migrations on a Lino site

Lino applications don't deliver out-of-the-box Django migrations because the database schema of a Lino site may also depend on local settings. For example the languages setting affects your database structure. Or you may locally disable a plugin. Or some plugin options can cause the structure to change.

Which just means that you must always run makemigrations before running migrate.

The migrations directory

migrations

Django migrations are automatically enabled on a Lino site when its site_dir has a subdirectory named migrations. Lino then automatically sets the migrations_package to the corresponding Python package name derived from the DJANGO_SETTINGS_MODULE.

Running pm prep without Django migrations

Let's use the lino_book.projects.migs project to play with migrations.

>>> from atelier.sheller import Sheller
>>> shell = Sheller("lino_book/projects/migs")

We begin with Django migrations disabled:

>>> shell("cat clean.sh")
#!/usr/bin/env bash
set -e
rm -rf migrations
rm -f default.db
echo "Removed migrations and database."
>>> shell("./clean.sh")
Removed migrations and database.

The pm prep command works also when Django migrations are disabled. In this context Django considers all Lino plugins as "unmigrated". Only some native Django plugins (contenttypes, sessions, staticfiles) are managed by Django:

>>> shell("python manage.py prep --noinput")
... 
`initdb std minimal_ledger demo demo2 demo_bookings checksummaries checkdata` started on database .../default.db.
...
Operations to perform:
...
Synchronizing apps without migrations:
...
Running migrations:
...
Loading data from ...
...
Update summary data ...
...
27 checks have been run. Found 0 and fixed 26 problems.
Installed 3840 object(s) from 33 fixture(s)

Tidy up:

>>> shell("./clean.sh")
Removed migrations and database.

Running pm prep with Django migrations

We enable Django migrations by creating an empty migrations directory (in the lino.core.site.Site.site_dir).

>>> shell("mkdir migrations") # same as settings.SITE.site_dir.mkdir("migrations")

When Django migrations are enabled, the pm prep command does the same, but in a different way. Django now considers all Lino plugins as "migrated":

>>> shell("python manage.py prep --noinput")
... 
`initdb std minimal_ledger demo demo2 demo_bookings checksummaries checkdata` started on database .../default.db.
...
Installed 3840 object(s) from 33 fixture(s)
>>> from lino import startup
>>> startup("lino_book.projects.migs.settings")
>>> from lino.api.doctest import *

The application developer can see whether Django migrations are enabled or not by looking at the migrations_package site attribute.

>>> print(settings.SITE.migrations_package)
lino_book.projects.migs.migrations

When Django migrations are enabled, Lino automatically fills the migrations directory with many subdirectories (one for each installed plugin) and sets the MIGRATION_MODULES setting.

>>> pprint(settings.MIGRATION_MODULES)
... 
{'about': 'lino_book.projects.migs.migrations.about',
 'appypod': 'lino_book.projects.migs.migrations.appypod',
 'bootstrap3': 'lino_book.projects.migs.migrations.bootstrap3',
 'cal': 'lino_book.projects.migs.migrations.cal',
 'calview': 'lino_book.projects.migs.migrations.calview',
 'changes': 'lino_book.projects.migs.migrations.changes',
 'channels': 'lino_book.projects.migs.migrations.channels',
 'checkdata': 'lino_book.projects.migs.migrations.checkdata',
 'comments': 'lino_book.projects.migs.migrations.comments',
 'contacts': 'lino_book.projects.migs.migrations.contacts',
 'countries': 'lino_book.projects.migs.migrations.countries',
 'dashboard': 'lino_book.projects.migs.migrations.dashboard',
 'excerpts': 'lino_book.projects.migs.migrations.excerpts',
 'export_excel': 'lino_book.projects.migs.migrations.export_excel',
 'extjs': 'lino_book.projects.migs.migrations.extjs',
 'gfks': 'lino_book.projects.migs.migrations.gfks',
 'groups': 'lino_book.projects.migs.migrations.groups',
 'help': 'lino_book.projects.migs.migrations.help',
 'invoicing': 'lino_book.projects.migs.migrations.invoicing',
 'jinja': 'lino_book.projects.migs.migrations.jinja',
 'ledger': 'lino_book.projects.migs.migrations.ledger',
 'lino': 'lino_book.projects.migs.migrations.lino',
 'linod': 'lino_book.projects.migs.migrations.linod',
 'lists': 'lino_book.projects.migs.migrations.lists',
 'memo': 'lino_book.projects.migs.migrations.memo',
 'nicknames': 'lino_book.projects.migs.migrations.nicknames',
 'noi': 'lino_book.projects.migs.migrations.noi',
 'notify': 'lino_book.projects.migs.migrations.notify',
 'office': 'lino_book.projects.migs.migrations.office',
 'pages': 'lino_book.projects.migs.migrations.pages',
 'printing': 'lino_book.projects.migs.migrations.printing',
 'products': 'lino_book.projects.migs.migrations.products',
 'publisher': 'lino_book.projects.migs.migrations.publisher',
 'rest_framework': 'lino_book.projects.migs.migrations.rest_framework',
 'restful': 'lino_book.projects.migs.migrations.restful',
 'sales': 'lino_book.projects.migs.migrations.sales',
 'smtpd': 'lino_book.projects.migs.migrations.smtpd',
 'storage': 'lino_book.projects.migs.migrations.storage',
 'subscriptions': 'lino_book.projects.migs.migrations.subscriptions',
 'summaries': 'lino_book.projects.migs.migrations.summaries',
 'system': 'lino_book.projects.migs.migrations.system',
 'tickets': 'lino_book.projects.migs.migrations.tickets',
 'tinymce': 'lino_book.projects.migs.migrations.tinymce',
 'topics': 'lino_book.projects.migs.migrations.topics',
 'uploads': 'lino_book.projects.migs.migrations.uploads',
 'users': 'lino_book.projects.migs.migrations.users',
 'userstats': 'lino_book.projects.migs.migrations.userstats',
 'vat': 'lino_book.projects.migs.migrations.vat',
 'weasyprint': 'lino_book.projects.migs.migrations.weasyprint',
 'working': 'lino_book.projects.migs.migrations.working',
 'xl': 'lino_book.projects.migs.migrations.xl'}

TODO: write tests to show a site upgrade using Django migrations.