Django Migrations

我们已经开始运行了一个简单的Django应用,你会注意到为了构建应用数据库,我们使用了命令 python manage.py migrate

Migrations概念

所谓 Migrations 是指Django将models的修改(添加字段、删除模型)转换到数据库schema(模式)的方法。migrate通常设计是自动的,但是你需要如何创建migrations,何时需要运行migrate,以及出现问题时如何解决。

migrations流程

  • 构建 model

在Django中,我们不需要手工去创建数据库,而是先构建一个 model ,即在 models.py 中我们定义数据库结构

from django.db import models

class User(models.Model):
    username = models.CharField(max_length=19)
    email = models.CharField(max_length=100)
    groups = models.CharField(max_length=100)
    create_time = models.DateTimeField()

    class Meta:
        ordering = ('create_time',)
  • 然后我们执行 makemigrations 创建对应的数据库initial.py:

执行 makemigrations
python manage.py makemigrations

此时会自动在 migrations 目录下生成生成一个 0001_initial.py 文件,类似

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='GuestPanic',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('username', models.CharField(max_length=19)),
                ('email', models.CharField(max_length=100)),
                ('gruops', models.CharField(max_length=100)),
                ('create_time', models.DateTimeField()),
            ],
            options={
                'ordering': ('create_time',),
            },
        ),
    ]
  • 执行数据库操作:

    python manage.py migrate
    

这个命令会根据生成的 0001_initial.py 对数据库进行操作,创建表格和对应字段。

  • 检查项目的migrations以及状态:

    python manage.py showmigrations
    

这个命令会输出所有的migrations以及状态(是否执行过)

makemigrations 报错处理

我在最近的一次实践中,对一个老项目重新部署,在完成数据库初步准备之后(创建数据库以及设置好账号密码),执行 python manage.py makemigrations 出现报错:

执行 makemigrations 报错
Traceback (most recent call last):
  File "/home/admin/onesre/core/manage.py", line 22, in <module>
    main()
  File "/home/admin/onesre/core/manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/management/base.py", line 412, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/management/base.py", line 453, in execute
    self.check()
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/management/base.py", line 485, in check
    all_issues = checks.run_checks(
                 ^^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/checks/registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/checks/urls.py", line 14, in check_url_config
    return check_resolver(resolver)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/core/checks/urls.py", line 24, in check_resolver
    return check_method()
           ^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/urls/resolvers.py", line 494, in check
    for pattern in self.url_patterns:
                   ^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/urls/resolvers.py", line 715, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
                       ^^^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/urls/resolvers.py", line 708, in urlconf_module
    return import_module(self.urlconf_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/admin/onesre/core/core/urls.py", line 21, in <module>
    path('api/', include('api.urls')),
                 ^^^^^^^^^^^^^^^^^^^
  File "/home/admin/onesre_venv3/lib/python3.11/site-packages/django/urls/conf.py", line 38, in include
    urlconf_module = import_module(urlconf_module)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/admin/onesre/core/api/urls.py", line 1, in <module>
    from django.conf.urls import url, include
ImportError: cannot import name 'url' from 'django.conf.urls' (/home/admin/onesre_venv3/lib/python3.11/site-packages/django/conf/urls/__init__.py)

这个问题在 ImportError: cannot import name ‘url’ from ‘django.conf.urls’ after upgrading to Django 4.0 有解释,原因是Django 3.0升级到Django 4.0+之后已经废弃了 django.conf.urls.url() 。由于是老项目,我采用 pip (Python包管理器) Downgrade Django版本方式来解决

Migrations后端支持

Migrations屏蔽了Django使用的数据库后端差异,通过完全相同的 model ,我们可以配置不同的数据库后端,实现对不同数据库的schema构建和修改。

清空数据和重新migrations同步

在开发过程中,我们可能会需要清空数据库并重新migrate,步骤如下

  • 删除项目的数据库表,这里举例是 api 项目(如果要保留数据,可以不执行这步)

  • 删除项目的migrations目录下所有文件,但保留 __init__.py

  • 重建migrate初始化文件:

    python manage.py makemigrations
    
  • 检查migrate状态(这里 api 是项目名字):

    python manage.py showmigrations api
    
  • 因为之前已经执行过migrate命令,所以同名的migrate都是已经执行状态,我们需要重置成空的状态:

    python manage.py migrate --fake api zero
    

然后再次检查migrate状态就会看到 api 对应的migrate状态是空的:

python manage.py showmigrations api
  • 重新生成migrate文件:

    python manage.py makemigrations api
    

此时重新生成的 0001_initial.py 文件会反映修订过的 models.py 内容(假如你调整了数据库表结构)

  • 重新执行数据库同步:

    python manage.py migrate
    

备注

如果你只想重新生成migrate文件,但是不执行到数据库,则使用:

python manage.py --fake-initial api

参考