2025, Nov 16 00:04

Почему миграции во второй базе SQLite в Django «ОК», а изменений нет

Подключили вторую SQLite в Django, а миграции «успешны», но схема не меняется? Рассказываем причину и даем точную команду: migrate --database=archiver.

Подключить вторую базу данных SQLite к проекту Django нетрудно, но миграции могут казаться «успешными», при этом на деле ничего не меняя. Если приложение уверенно читает и пишет в дополнительную БД, но изменения схемы так и не применяются, скорее всего, вы запускаете миграции не на том подключении. Ниже — наглядный разбор ситуации и точное решение.

Воспроизводим конфигурацию и симптом

В проекте задействованы две базы: стандартная для внутренних нужд Django и отдельный файл SQLite для приложения archiver. Роутер направляет операции чтения и записи моделей этого приложения в БД archiver. Модели сгенерированы через inspectdb, немного подправлены, а первая миграция была помечена как применённая с флагом --fake. На вид всё связано правильно: через Django shell чтение и запись действительно попадают в archiver. Проблема проявляется при новой миграции, которая удаляет столбец: Django сообщает об успехе, но столбец остаётся в файле SQLite, а метка времени файла не меняется.

Ниже — минимальная версия роутера и настроек, использованных в этой конфигурации. Логика та же, названия слегка упрощены для наглядности.

# archiver/routers.py

class ArchiveRouterV2:

    def db_for_read(self, model, **hints):
        if model._meta.app_label in ['archiver']:
            return 'archiver'
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label in ['archiver']:
            return 'archiver'
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label in ['archiver']:
            return db == 'archiver'
        return None
# settings.py (фрагмент)

import os
from pathlib import Path
from dotenv import load_dotenv

load_dotenv()
USER_PATH = Path(os.getenv('USER_DIR', './user'))

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    },
    'archiver': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': USER_PATH / 'data/app.db',
    }
}

DATABASE_ROUTERS = ['archiver.routers.ArchiveRouterV2']

Доступ к данным работает как и задумано, что подтверждает корректную маршрутизацию. Например, следующий save попадает в базу archiver:

>>> rec = State(account="test", name="test", value="test")
>>> rec.save()

После удаления неиспользуемого поля из модели Post создаётся миграция:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('archiver', '0001_initial'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='post',
            name='note',
        ),
    ]

Запуск migrate выводит статус OK, но столбец в файле остаётся.

Почему ничего не меняется

Всё становится ясно по выводу sqlmigrate. Если попросить Django показать SQL для базы archiver, получится корректный ALTER TABLE. Тот же запрос без указания базы даёт пустую операцию.

$ python manage.py sqlmigrate archiver 0002 --database=archiver
BEGIN;
--
-- Удаление поля note из post
--
ALTER TABLE "posts" DROP COLUMN "note";
COMMIT;
$ python manage.py sqlmigrate archiver 0002
BEGIN;
--
-- Удаление поля note из post
--
-- (нет операции)
COMMIT;

По умолчанию migrate работает с базой default. В такой конфигурации для приложения archiver получается пустой SQL — отсюда и «применено» без реальных изменений схемы во второй SQLite-базе.

Решение

Для недефолтного подключения явно указывайте целевую базу при запуске миграций. Эта команда применит изменения к нужной БД и обновит файл, как и должно быть:

python manage.py migrate archiver --database=archiver

Почему это важно

В проектах с несколькими базами легко впасть в самоуспокоенность: чтение и запись корректно идут через роутер, а вот операции со схемой в командах manage.py автоматически базу не переключают. Эту асимметрию легко не заметить, особенно с SQLite, где «пустая» миграция на базе по умолчанию завершается без шума.

Практические выводы

Управляя изменениями схемы для неосновной базы, всегда передавайте флаг --database в migrate и sqlmigrate. Если миграция как будто применена, но на диске ничего не меняется, посмотрите SQL для нужной базы и убедитесь, что команда работает по тому же алиасу. Эта привычка избавит от тихих «пустых» прогонов и сохранит согласованность моделей и вторичного файла SQLite.