Django_database migrations 数据库迁移及其相关若干问题/迁移问题排查/迁移和sql语句预览/添加non_null字段到模型/迁移版本回滚/重新执行(roll back)

Django_database migrations 数据库迁移及其相关若干问题/迁移问题排查/迁移和sql语句预览

references

使用Django的个人习惯

  • 一般的,数据库表设计完后就不易改动(所以在设计环境应该仔细和慎重)
    • 至少不宜在敲定之前存放过多数据(存放少量测试数据)
    • 数据库表名(模型名)需要严格统一,在python下编程,建议使用下划线分割不同单词(而不是驼峰命名法)
  • 但是某些时候,(譬如需求的变更),我们需要修改数据库表
  • 及时的使用git打个快照,方便恢复项目
  • 外键和其他数据库约束的设置
    • 据了解,人们较多的认为,在数据库层面设置外键之类的约束对于性能是不友好的
    • 实际操作中,使用其他约束逻辑来代替数据库上的约束
      • 在绘制ER图的时候,应当要体现外键(如果合适的化)
      • 关于主键:
        • 如果使用的是mysql,那么主键默认情况下不区分大小写,这意味着,如果您的候选主键要求区分大小写的时候,可能就需要另外设置主键(譬如AutoField);(当然,也可以配置msql,使得其区分大小写(例如uft8-bin,但是据django文档介绍,该配置可能造成出乎意料的结果(对于ORM不友好))
    • 此外,正如上面提到的,在django(等ORM)中,一旦外键关系复杂(或者使用较多),对于重构模型是很不利的

常用迁移命令(manage.py&django-admin)

  • 如果您的项目基于虚拟环境建立的,那么每当执行manage.py等命令时都应该先激活虚拟环境,否则会出现出乎意料的执行结果
  • django-admin and manage.py | Django documentation | Django (djangoproject.com)

  • django-admin and manage.py

    • django-admin is Django’s command-line utility for administrative tasks. This document outlines all it can do.(该命令不是某个django项目独有)

      • 该命令比较适合于需要在多个项目之间切换执行(环境变迁),执行时需要环境参数
    • In addition, manage.py is automatically created in each Django project.

      • 每个项目都有自己的manage.py脚本文件来执行命令
      • 单一项目中首选
      • It does the same thing as django-admin but also sets the DJANGO_SETTINGS_MODULE environment variable so that it points to your project’s settings.py file.

    The django-admin script should be on your system path if you installed Django via pip.

    If it’s not in your path, ensure you have your virtual environment activated.

    • Generally, when working on a single Django project, it’s easier to use manage.py than django-admin.
    • If you need to switch between multiple Django settings files, use django-admin with DJANGO_SETTINGS_MODULE or the --settings command line option.
    • The command-line examples throughout this document use django-admin to be consistent,
    • but any example can use manage.py or python -m django just as well.
    • $ django-admin <command> [options]
      $ manage.py <command> [options]
      $ python -m django <command> [options]

关于迁移的一些问题&参考

清理migrations表

清理migrations实操
PS D:\repos\ELA\backEnd\ela> .\manage.py migrate --fake word zero
Operations to perform:
Unapply all migrations: word
Running migrations:
Rendering model states... DONE
Unapplying word.0002_rename_uid_wordnotes_user... FAKED
Unapplying scoreImprover.0001_initial... FAKED
Unapplying word.0001_initial... FAKED
PS D:\repos\ELA\backEnd\ela> pmg showmigrations word
word
[ ] 0001_initial
[ ] 0002_rename_uid_wordnotes_user
[ ] 0003_alter_wordnotes_user
[ ] 0002_remove_wordnotes_uid_wordnotes_user
[ ] 0004_merge_20220522_1411
PS D:\repos\ELA\backEnd\ela> cd .\word\migrations\
PS D:\repos\ELA\backEnd\ela\word\migrations> ls
Directory: D:\repos\ELA\backEnd\ela\word\migrations
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2022/5/22 14:12 __pycache__
-a--- 2022/5/19 17:57 0 __init__.py
-a--- 2022/5/19 18:58 3432 0001_initial.py
-a--- 2022/5/22 14:09 700 0002_remove_wordnotes_uid_wordnotes_user.py
-a--- 2022/5/22 14:11 365 0002_rename_uid_wordnotes_user.py
-a--- 2022/5/22 14:09 613 0003_alter_wordnotes_user.py
-a--- 2022/5/22 14:11 300 0004_merge_20220522_1411.py
PS D:\repos\ELA\backEnd\ela\word\migrations> rm 00*;ls
Directory: D:\repos\ELA\backEnd\ela\word\migrations
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2022/5/22 14:12 __pycache__
-a--- 2022/5/19 17:57 0 __init__.py
  • 可以自助查看一下刚才用到的migrate的帮助手册

  • python manage.py migrate -h

django 反复提示talbe already exsit

  • 这种容易情况发生在有多个数据库连接配置的情况
  • 如果您已经将提示已存在的表手动的删除,还是会提示,那么可能是您手动操作的数据库和当前django连接(启用的数据库不是同一个!)

django 迁移的基本特点说明

模型迁移的部分执行导致的错误

  • django 的迁移不像原子性事务,某一次迁移复杂任务的执行可能前部半部分是顺利的(只执行了前半部分的任务),但是后半部分却发生错误,导致实际数据库表结构发生不完整变更(导致无法迁移回滚,陷入进退两难的境地),因此,执行稍微复杂的迁移任务前,请务必慎重,甚至要读一下生成的migrations文件
  • 一般的,每次改动不应该过大;譬如,我们想要将一个字段更改为外键(带有外键约束),那么就不要同时修改字段名,否则数据库的字段可能被删除了,但是期待的外键字段没有生成(这种情况下执行的不是字段更名(rename),而是被django解释为:移除旧有字段,并增加一个新字段,这个新字段带有外键约束
  • 譬如我的一次失误操作:(同时更名以及添加外键约束)
migrations.RemoveField(
model_name='wordnotes',
name='uid',
),
migrations.AddField(
model_name='wordnotes',
name='user',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='user.user'),
preserve_default=False,
),

添加独一无二字段的迁移(add unique OR non-null 字段的方法

  • 备份数据后在导入到新数据模型是相对万能的办法,
  • 但是下面会引入一些变通的方法试图,稍作修改就能够达到migration Model schema的同步目的
add unique 字段

如果当前模型已有的数据不想删除,并且不想备份后从新导入到新模型中,那么还有如下变通方法(操作相对简单)

额外的可能的方案
  • 为当前创建一个普通字段(模型属性),该字段要求宽松(没有unique约束,并且,我们可以为其选择一个默认值(根据情况,转换类型为 字符串/数值)
  • 迁移完成后,数据库中有相应字段
  • 通过编写脚本来修改这个新字段(可以导入你准备好的互不相同的数据列表,或者借助算法(生成一批不同的值
    • (最简单的就是range(size))生成
    • 如果需要字符串,那么在用str()函数转一下类型
  • 现在,我们再做一次模型修改(增加unique约束),并且再执行一遍迁移(使得unique约束对后续的数据库操作生效)

误删migrations文件夹

  • 一般的,django 执行迁移操作前会检查数据库中的django-migrations 表;

  • 并结合项目中的migrations中的迁移文件来执行同步操作

    • 某些复杂的模型改动会导致makemigrations无法正确的反应我们的修改,导致数据库无法同步模型的更改
    • 粗暴的做法是使用migrate --fake zero 来清空对应该模型迁移再django-migrations的中的记录(此清空操作不会影响数据库中的其他表的数据)
  • 如果误删migrations目录,那么手动创建一个migrations目录(并在里面创建空文件__init__.py或许是有效的方法
    然而,有些时候情况会比较复杂,比如其他的模型以往的迁移过程依赖于这个被删除的migrations目录中的某个迁移文件,将导致无法直接重建被删除的数据库(因为在尝试执行makgemigrations是,django会一并检查其他
    app的模型(表的迁移依赖是否正常)

  • (ll_env) PS D:\repos\IdeaProjects\djangoProjects\ela_old\ela> pmgmk
    Migrations for 'blog':
    blog\migrations\0026_alter_entry_body_text.py
    - Alter field body_text on entry
    Migrations for 'words':
    words\migrations\0001_initial.py
    - Create model WordNotes
    - Create model Words
    - Create model NeepWordsReq
    - Create model Cet6WordsReq
    - Create model Cet4WordsReq
  • 某些复杂的场合,甚至需要手动修改迁移文件(migrations中的文件)

django迁移问题的暴力解决方案

添加 non-null字段到模型

How to Solve You are trying to add a non-nullable field to without a default (pytutorial.com)

  • 如果迁移时报错

It is impossible to add a non-nullable field ‘id’ to wordsearchhistory without specifying a default. This is because the database needs something to populate existing rows.

  • 且,如果要更换的是主键,那么简单的设置字段构造器的参数是无法解决的,但是,如果被更换的旧主键字段不是其他模型中的外键,那么可以通过下述步骤来重新建立模型&建表:
    • Deleting and returning the model
      Before talking about this way, I’d to warn you that this way will delete your model data.
      so you need to delete your model class in models.py then migrate your app models, after migrations return back the model and migrate the model

  • backup Model & Delete Model(备份以下模型代码,如果已经有数据,可以一同备份数据,然后从models.py中将该模型类删除掉) =>
  • Makemigrations Models =>
  • Migrate Models =>
  • Return Back the models(重新将模型写入到models.py) =>
  • Makemigrations Models =>
  • Migrate Models

如果不出意外,可以看到模型&数据库表同步完成

虚拟外键

  • 将外键行设置为虚拟外键
    (db_constraint=False)
    • 例如: user = models.ForeignKey(User, on_delete=models.DO_NOTHING, db_constraint=False)
  • django migration出来的sql操作并不够专业(比较粗糙)(这也是Django的模型通用性带来的粗糙特征))
  • 专门的DBA设计出来的表格更加专业,这使得我们对于Migration的使用不那么依赖
    • 只需要注意模型中的表名字/字段和DBA的表相匹配即可操作表
      • 当然,不迁移也是可以操作数据库的
    • 换句话说,生产环境中,我们根据DBA建立的专业的数据库表来编写对应的Django模型.
    • 尽管如此,迁移意味着我们可以回滚数据库,但这种行为本身就是代价高的,争取早期的完善设计&数据备份会更加通用

某些同步问题着实令人沮丧

  • 可以采取如流程恢复同步:
    数据库表数据备份(重命名旧表可以当作是备份,如果想直接删除旧表重建也行)
    备份最新模型代码(models.py)

  • 清理已有的migrations迁移文件

    • 检查app当前的迁移情况python manage.py showmigrations <app-name>

      • 如果不指定app-name,将会列出所有app的migration情况
    • 清除所有迁移记录:python manage.py migrate --fake <app-name> zero

      • 重新showmigrations来检查清除的情况
      • showmigrations会检查app中的migrations目录来提供报告

    • 删除migrations目录内部的所有py文件(仅保留__init__.py)

    • 重新执行迁移:

      • 确保前面数据库旧表的重命名或者(备份后删除)
      • python manage.py makemigrations <app-naem>
      • python manage.py migrate <app-name>
      • 上述操作将尝试通过django创建全新的数据库表
      • 最后登录到数据库检查新表(gui工具的话需要手动刷新)(命令行查看更加及时)

  • 原生的使用django来创建数据库库表,比较不容易出错

  • 对于已经有数据的表,将数据备份出来,再做导入

  • 这是对于项目时间紧迫的情况下的策略,

    从认真学习的角度,应该通过了解migratetion的工作方式采取更好的方法来处理冲突和同步问题

Django migration

Migrations

简介

  • Migrations are Django’s way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema.
  • They’re designed to be mostly automatic, but you’ll need to know
    • when to make migrations,
    • when to run them, and the common problems you might run into.
makemigrations & migrations文件
migrations 文件

¶Django migrations file| djangoproject.com

  • You should rarely, if ever, need to edit migration files by hand, but it’s entirely possible to write them manually if you need to.
  • Some of the more complex operations are not autodetectable and are only available via a hand-written migration, so don’t be scared about editing them if you have to.

The Commands for check migrations(迁移状态&故障排查工具)

There are several commands which you will use to interact with migrations and Django’s handling of database schema:

  • migrate, which is responsible for applying and unapplying migrations.
  • makemigrations, which is responsible for creating new migrations based on the changes you have made to your models.

sqlmigrate:检查migrations对应的sql操作

references
sqlmigrate概述
  • 这个工具很有用,当您发现某些创建表的迁移并没有安装预期的方式工作,那么很可能是django没有将migration中的任务转换为正确的mysql语句

  • 本命令需要连接数据库(而不仅仅是根据本地的模型迁移),来生成对应数据库种类的sql语句

这经常是Model Meta中的配置字段managed的取值问题

  • 尤其是这些模型是您从类似于mysql数据导出的时候,需要特别关注.
  • sqlmigrate

    • Model Meta options | Django documentation | Django (djangoproject.com)

    • python manage.py sqlmigrate app_label migration_name

      • 注意,执行次命令的时候是以python manage.py打头来启动(如果想不指定项目的话)

      • 如果使用django-admin,而没有指定环境参数,则会导致报错

      • raise ImproperlyConfigured(
        django.core.exceptions.ImproperlyConfigured: Requested setting TEMPLATES, but settings are not configured.
        You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
    • Prints the SQL for the named migration.

    • This requires an active database connection, which it will use to resolve constraint names;

    • this means you must generate the SQL against a copy of the database you wish to later apply it on.

    Note that sqlmigrate doesn’t colorize its output.

    sqlmigrate 的选项:

    • --backwards

      • Generates the SQL for unapplying the migration.
      • By default, the SQL created is for running the migration in the forwards direction.
    • --database DATABASE`

      • Specifies the database for which to generate the SQL.
      • Defaults to default.

    Specifies the database for which to generate the SQL. Defaults to default.

例如
  • python manage.py sqlmigrate polls 0001

  • (ll_env) PS D:\repos\IdeaProjects\djangoProjects\ela_old\ela> py manage.py sqlmigrate word 0001
    --
    -- Create model Word
    --
    --
    -- Create model WordNotes
    --
    --
    -- Create model Cet4WordsReq
    --
    CREATE TABLE `cet4_words_req` (`wordOrder` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `spelling` varchar(255) NOT NULL);
    --
    -- Create model Cet6WordsReq
    --
migrations 中的迁移文件内容示例
  • migrations.CreateModel(
    name='Word',
    fields=[
    ('wid', models.AutoField(primary_key=True, serialize=False)),
    ('spelling', models.CharField(max_length=255)),
    ('phonetic', models.CharField(blank=True, max_length=255, null=True)),
    ('plurality', models.CharField(blank=True, max_length=255, null=True)),
    ('thirdpp', models.CharField(blank=True, max_length=255, null=True)),
    ('present_participle', models.CharField(blank=True, max_length=255, null=True)),
    ('past_tense', models.CharField(blank=True, max_length=255, null=True)),
    ('past_participle', models.CharField(blank=True, max_length=255, null=True)),
    ('explains', models.TextField(blank=True, null=True)),
    ],
    options={
    'db_table': 'words',
    'managed': False,
    },
    ),
    migrations.CreateModel(
    name='WordNotes',
    fields=[
    ('id', models.BigAutoField(primary_key=True, serialize=False)),
    ('wordspelling', models.CharField(blank=True, db_column='wordSpelling', max_length=255, null=True)),
    ('uid', models.IntegerField(blank=True, db_column='UID', null=True)),
    ('content', models.CharField(blank=True, max_length=255, null=True)),
    ('difficulty_rate', models.IntegerField(blank=True, null=True)),
    ],
    options={
    'db_table': 'word_notes',
    'managed': False,
    },
    ),
  • 出现sql语句为空的create操作正是由于option中managed取值所造成的
  • 但是建议您从Model中修改,然后重新makemigrations
  • 可以配合下方的showmigrations命令中的列表来查看指定某个变迁所对应的sql语句映射
  • 一般的,initial文件中记录的是初次依照app中的各个模型来创建对应的数据库表的create语句

showmigrations

showmigrations, which lists a project’s migrations and their status.

  • 注意,执行此命令的时候是以python manage.py打头

    • 如果使用django-admin会导致报错

    • raise ImproperlyConfigured(
      django.core.exceptions.ImproperlyConfigured: Requested setting TEMPLATES, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
  • 以app 为单位进行显示(我就python manage.py设置函数别名为:pmg)

  • cxxu➜~/djangoProjects/ela(main✗)» pmg showmigrations [18:57:33]
    admin
    [X] 0001_initial
    [X] 0002_logentry_remove_auto_add
    [X] 0003_logentry_add_action_flag_choices
    auth
    [X] 0001_initial
    [X] 0002_alter_permission_name_max_length
    contenttypes
    [X] 0001_initial
    [X] 0002_remove_content_type_name
    scoreImprover
    [X] 0001_initial
    sessions
    [X] 0001_initial
    user
    [X] 0001_initial
    [X] 0002_remove_user_test_alter_user_examdate_and_more
    [X] 0003_alter_user_examdate_alter_user_examtype
    words
    [X] 0001_initial

migrate(简介)

manage shell:检查django和数据库的连接

  • 利用python manage.py shell来让django启动一个交互式python终端

    • 在里面可以快速的检查数据库对接情况

    • 以及模型定义的字段和参数是否符合预期(譬如默认值)

    • (ll_env) PS D:\repos\ELA\backEnd\ela> pmg shell
      Python 3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)] on win32
      Type "help", "copyright", "credits" or "license" for more information.
      (InteractiveConsole)
      >>> from user.models import User,UserInfo
      >>>
      >>> User.objects.create(name="testScriptUser")
      <User: [5, 'testScriptUser', 0, '1970-01-01', '4', '1970-01-01']>
      • 交互式脚本可以专门建立一个目录在里头创建脚本文件编写,以便利用IDE的补全功能

models.py文件与模型构建

  • models.py 是定义数据库表结构的python文件
  • models.py不要放置非Model& subClass定义的其他代码,多余的代码会导致makemigrations 出现问题.(譬如提示某数据库表格不存在等问题)

  • 为模型编写__str__函数是一个好习惯

    • 譬如

    • def __str__(self):
      s=self
      return str([s.uid,s.name,s.signin,s.examdate,s.examtype,s.signupdate])
    • 可以在打印模型对象(一条记录)的时候看到模型中包含的主要内容

字段的默认值
  • 字段的默认值似乎无法体现在数据库中,而是体现在django中的模型
    • 推测应该是在插入记录的时候,django会自动视情况给模型中设置了default参数的对象字段赋值
    • 而且,应该以django中的默认值准(覆盖数据库中的默认值)
    • 换句话说,django中设置的默认值仅仅对于django自己操作数据库时才生效,不影响原生数据库中为字段设置的默认值.
外键
  • 外键(多对一),在MySQL数据库中的表的对应的sql描述中没有ForeignKey 约束
  • 默认的,会建立索引(Create Index)
  • 约束有django模拟

makemigrations

  • 该命令可以检查django app的models.py 文件中是否发生变更,并生成对应的数据库变迁操作,来同步数据库;
    • 这些检查出来的变化所对应的模型操作会记录在文件中,存放在app的migrations目录下
  • 在执行该命令的时候,应当加上具体的app 名称,以保证makemigrations 过程能够正确执行

从已有的mysql数据库迁移到django项目中(django import exsited databases from mysql)

  • 曾有过此需求,等有空实践一下

reference1

  • 从django中创建模型并同步到尚且为空的某种数据库(譬如mysql/postgre)中这种方向是简单的

  • 但是某些时候,我们的表在数据库中已将存在,而想让后来创建的django来对接并能够应用django.db模型来管理(同时应用上迁移(migration)等特性),需要注意若干问题

      • New apps come preconfigured to accept migrations, and so you can add migrations by running makemigrations once you’ve made some changes.

      • If your app already has models and database tables, and doesn’t have migrations yet (for example, you created it against a previous Django version), you’ll need to convert it to use migrations by running:

      • 如果是直接从mysql导出,也需要先将mysql数据库导出为django Model(inspectdb命令)

      $ python manage.py makemigrations your_app_label
      • This will make a new initial migration for your app.
      • Now, run python manage.py migrate --fake-initial, and Django will detect that you have an initial migration and that the tables it wants to create already exist, and will mark the migration as already applied.
      • (Without the migrate --fake-initial flag, the command would error out because the tables it wants to create already exist.)

      Note that this only works given two things:(仅在满足以下条件时,上述操作才有效)

      • You have not changed your models since you made their tables. For migrations to work, you must make the initial migration *first* and then make changes, as Django compares changes against migration files, not the database.
      • You have not manually edited your database - Django won’t be able to detect that your database doesn’t match your models, you’ll just get errors when migrations try to modify those tables.
    • 确保initial(–fake-initial)

reference2

Create the Django models from MySQL

The above below will produce django models in models.py from tables present in MySQL.

$ python manage.py inspectdb > models.py

Modifying database tables

  • Legacy tables are not permitted to be modified by default.
    • But they can be modified if needed.
  • First set managed=True in sub Meta classes of classes you want to modify.
  • And then, add the new field you want.
class Meta:
managed = True
db_table = 'customers'

Now, make the migrations file.

python manage.py makemigrations

Since the tables already exist, we need a fake migration.

python manage.py migrate --fake

Voila! Now the new table field should be added!

posted @   xuchaoxin1375  阅读(164)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· DeepSeek “源神”启动!「GitHub 热点速览」
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示