使用真实示例编写 Django 数据迁移 django-test-migrations

2025-06-08

使用真实示例编写 Django 数据迁移

django 测试迁移

大多数情况下,我们在 Django 中提到迁移时,指的是模式迁移。Django 可以自动创建这些迁移,因为它们描述的是数据库结构的变更,而不是数据本身的变更。然而,您可能还会用到另一种迁移类型,那就是数据迁移。当您加载新数据或想要使用特定模式更改数据库中的数据时,数据迁移非常有用。

我在构建ickly(一个用于纽约市餐厅卫生检查数据的搜索界面)时遇到了这个问题。我希望我的应用用户能够按名称搜索餐厅并查看其所有检查数据。数据集是一个 CSV 文件,其中的行与检查记录相对应,但是它有一个“camis”字段,它是商家的唯一标识符。我希望转换这些数据以匹配我想要的“商家和检查”数据模型,并且需要获取所有唯一的商家信息。

如果您只是加载一个装置或一些已经在您需要的结构中的示例数据,那么您可能不需要数据迁移,但可以使用loaddataDjango 提供的命令。

创建数据迁移

Django 无法自动生成数据迁移,但您可以自己编写一个。您可以运行以下命令生成一个空的迁移文件,并在其中添加操作。

python manage.py makemigrations --empty yourappname

我们将要讨论的主要操作,也是您将用于数据迁移的操作是RunPython。自动生成的文件如下所示:

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations

class Migration(migrations.Migration):

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

    operations = [
    ]

RunPython需要一个可调用函数作为其参数。您将编写的这个函数接受两个参数:一个应用程序注册表和一个模式编辑器。然后,我们将操作传递给函数。这将导致它在我们从命令行RunPython运行时被执行。./manage.py migrate

from django.db import migrations

def my_function(apps, schema_editor):
    # logic will go here
    pass


class Migration(migrations.Migration):

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

    operations = [
        migrations.RunPython(my_function),
    ]

应用注册表维护着所有可用模型的历史版本列表。我们希望在函数中使用应用注册表来获取历史版本,apps.get_model('your_app_name', 'your_model_name)而不是直接导入模型。这样做是为了确保我们使用的模型版本符合本次迁移的要求。如果您使用直接导入,则可能会导入较新的版本。

SchemaEditor 可用于手动更改数据库架构。除非是极其复杂的情况,否则您很可能不想直接与它交互。SchemaEditor 将操作公开为方法,并将诸如“创建模型”或“修改字段”之类的操作转换为 SQL。

RunPython 操作还可以接受第二个可调用函数。第二个函数包含您希望在向后迁移时发生的逻辑。如果您未提供该函数,则尝试向后迁移将引发异常。如果您想了解有关 RunPython 操作和其他可选参数的更多信息,请查看此处的文档。

例子

让我们看一个直接从我为 ickly 编写的代码中截取的迁移示例。我添加了注释,以指出我们在这篇文章中讨论过的所有相关部分。

# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-04-20 21:02
from __future__ import unicode_literals

from django.db import migrations, models
import csv
from datetime import datetime


def load_initial_data(apps, schema_editor):
    # get the correct versions of models using the app registry
    Business = apps.get_model("api", "Business")
    Inspection = apps.get_model("api", "Inspection")

    # This is where your migration logic will go. 
    # For my use case i needed to get unique businesses and 
    # transform data from the csv file into the schema i wanted 
    with open('DOHMH_NYC_Restaurant_Inspection_Results.csv') as csv_file:
        reader = csv.reader(csv_file)
        header = next(reader)

        businesses = []
        inspections = []

        for row in reader:
            camis = row[0]
            business = next((b for b in businesses if b.camis == camis), None)
            if not business:
                business = Business(camis=row[0], name=row[1],
                            address="{} {} {} {}".format(row[3], row[4], row[2], row[5]),
                            phone=row[6], cuisine_description=row[7])
                businesses.append(business)

            inspection = Inspection(business=business,
                                record_date=datetime.strptime(row[16],"%m/%d/%Y").date(),
                                inspection_date=datetime.strptime(row[8],"%m/%d/%Y").date(),
                                inspection_type=row[17], action=row[9], violation_code=row[10],
                                violation_description=row[11], critical_flag=row[12],
                                score=int(row[13]) if row[13] else None,
                                grade=row[14],
                                grade_date = datetime.strptime(row[15],"%m/%d/%Y").date() if row[15] else None)
            inspections.append(inspection)

        Business.objects.bulk_create(businesses)
        Inspection.objects.bulk_create(inspections)

## logic for migrating backwards
def reverse_func(apps, schema_editor):
    Business = apps.get_model("api", "Business")
    Inspection = apps.get_model("api", "Inspection")

    Business.objects.all().delete()
    Inspection.objects.all().delete()

class Migration(migrations.Migration):
    # Django automatically adds dependencies for your migration
    # when you generate the empty migration
    dependencies = [
        ('api', '0002_auto_20170420_2101'),
    ]

    # the RunPython operation with the two callables passed in
    operations = [
        migrations.RunPython(load_initial_data, reverse_func)
    ]

关于 Django 数据迁移,还有很多需要了解的地方,但现在你已经掌握了相关知识,可以判断是否需要编写迁移代码,并知道如何开始编写。如果你想了解更多关于 Django 迁移的常规知识,文档提供了一个很好的概述

如果您有任何问题、评论或反馈,请告诉我。关注我,每周更新 JavaScript、React、Python 和 Django 的文章!

封面照片由Taylor Vick在 Unsplash 上拍摄

鏂囩珷鏉ユ簮锛�https://dev.to/guin/writing-a-django-data-migration-with-real-world-example-40m
PREV
掌握这些主题,你就能成为 JavaScript 面试高手 - 第 2 部分
NEXT
厌倦了猜测“这”指的是什么吗?