在编写Django迁移时停止丢失数据!

Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

001.png


说数据库结构很重要是一种明显的说法。如果你决定使用结构化的数据库技术的话,那是当然的了。但是在这种情况下,你希望你的数据库结构最接近你的功能和业务需求,并且是最紧密的,以便能够依赖于该结构。


但是,当我处理Django项目时,我有时会发现在需要的时候更改数据结构是非常痛苦的事情。但是,我发现Django迁移提供了一个非常有用的工具:RunPython命令。


简单的迁移

让我们举一个简单的例子,假设我们在orders应用程序中只有一个名为Order的模型。


然后我们将得到以下models.py文件:


001.jpg


运行 python manage.py makemigrations 将产生以下迁移文件,名为 0001_initial.py :


001.jpg


让我们来分析一下这个迁移文件中发生了什么。迁移操作以一个python类进行表示,带有3个属性:


  • initial 表明这个迁移是其Django应用程序的第一个迁移;

  • dependencies列出了在应用此迁移之前需要应用的所有迁移。这里它是空的,因为该迁移是其Django应用程序的第一个;

  • operations是最重要的部分。它是Django将会应用于数据库的一个操作列表。在这里该数据库是空的,因此我们使用CreateModel方法从头开始创建模型。


当要执行的操作足够简单时,python manage.py makemigrations会执行得很好。例如,重命名模型或重命名字段将被视为等效的数据库操作。


在应用迁移之后,让我们在数据库中创建一些实体。


001.png


复杂结构迁移


让我们再从前面的例子开始,观察一些你可能已经注意到的东西。customer_name、customer_address、customer_city和customer_zip_code四个字段被功能性地附加到Order实体,同时它们表示另一个物理实体,即客户。只要没有重复,这就没有问题。但是, 现在让我们假设出于业务目的,你需要抽象一个Customer实体,Order实体会被链接到该实体。


然后你的 models.py 文件将会是这样:


001.jpg


这个配置现在满足我们的功能性需求了。我们来尝试生成该迁移!


001.jpg


但是这里有一个问题。Django意识到我们正尝试在Order模型上创建一个customer字段,这个字段不能为None,但还没有被定义。因此,它会想要向数据库中所有现有的实体添加一个非空值,并询问我们是现在提供该值,还是想要在Order模型中设置它。


然而,这些选择都不能使我们满意。我们不希望我们的订单拥有的客户是None或者一个默认客户,这样的话会丢失所有现有的数据。


当然,我们也可以执行此迁移,然后手动设置所有Order模型的customer属性,但这将迫使你在你的所有环境中执行相同的操作。这将会使得迁移很难回滚。


幸运的是,Django提供了一个内置的解决方案来处理这种限制。


生成自定义迁移

首先,我们先生成一个空迁移,稍后再编辑它。


001.jpg


这个生成的迁移文件将看起来像这样:


002.jpg


然后,我们需要构建我们的自定义迁移。它需要有6个步骤:


  • 在数据库中创建新的Customer模型,包含所有必需的字段。

  • 在Order上创建一个可为空的customer外键

  • 将要传输的Order字段设置为可空

  • 将数据从Order模型传输到Customer模型。

  • 将Order上的新的customer字段设置为不可空

  • 从Order模型中删除旧字段。


Django文档解释了所需的所有数据库操作。


我们来编写这个迁移:


001.jpg

002.jpg


在这个迁移中,步骤2和3基本上放松了数据结构,为数据的传输做准备。然后步骤5和6再次收紧了数据!


现在我们来看步骤4,奇迹发生的地方!


编写数据传输函数

为了执行这个操作,我们将使用RunPython迁移操作。它的基本功能是使用ORM执行一个python函数。


它的语法如下:


001.jpg


在该迁移的这个步骤中,我们定义了两个函数:


  • order_to_customer 会在运行该迁移时被运行;

  • customer_to_order 会在反转迁移时被运行。


002.jpg


这些函数看起来很像你为手动将数据从一个模型传输到另一个模型所编写的函数。不过,这里有一个小技巧。使用from orders.models import Order, Customer正常导入模型是不可能的。相反,我们需要使用一个由Django传递给迁移函数的版本化模型。


运行迁移

在通过python manage.py migrate运行这个迁移之后,我们可以检查该迁移是否创建了Customer实例并将它们链接到了正确的Orders。


001.jpg


好了!这个迁移现在是运行的了。你还可以使用python manage.py migrate orders <previous_migration_id>命令轻松地对它进行回滚。然后数据将会被传输回Order模型。


结论

现在,你可以不用再担心使用Django迁移数据库时会丢失数据了!Django ORM是一个非常棒的工具,尽管它在大多数情况下都能完成工作,但是了解底层发生了什么是一种非常好的方式,可以使你在处理迁移时更加轻松。


资源

  • 关于迁移的Django文档:

  • https://docs.djangoproject.com/en/3.0/howto/writing-migrations/

  • https://docs.djangoproject.com/en/3.0/ref/migration-operations/

  • 到Github实例仓库的连接: https://github.com/fargito/django-runpyton

英文原文:https://blog.theodo.com/2020/05/django-migrations-without-losing-data/
译者:测试