如何用正确的姿势去创建或者迁移Django数据库

Django的ORM非常强大,用它进行数据迁移非常方便,它可以结合Models的变化来改变数据库中的数据。  Django跟踪依赖关系,执行顺序以及应用程序来确定应用是否已经进行了数据迁移。

下面我们用一个常见例子来进行举例。当我们需要引入不可为空的新字段时,或者,当我们创建一个新字段来存储缓存的某个值时,我们可以创建新字段并添加初始计数。

在这篇文章中,我们将探索一个简单的示例,您可以很容易地根据需要扩展和修改它。


假设我们有一个名为blog的应用程序,已经在INSTALLED_APPS里添加了!

Blog下的models是这样定义的:

blog/models.py

from django.db import models
class Post(models.Model):
    title = models.CharField(max_length=255)
    date = models.DateTimeField(auto_now_add=True)
    content = models.TextField()
    def __str__(self):
        return self.title

这应用程序已经在使用这个Post模型;它已经在生产中,数据库中存储了大量数据。

1.jpg

现在,假设我们想引入一个名为slug的新字段,该字段将用于组成博客的新url。slug必须是唯一(unique)而且不不能为空(not null)。

一般来说,我们添加的时候总是将新字段添加为null=True或使用默认值(default)。如果不能使用默认参数解决问题,那么首先将字段创建为null=True,然后为其创建数据迁移。然后,我们可以创建一个新的迁移,将字段设置为null=False。

修改过后的Models如下:

blog/models.py

from django.db import models
class Post(models.Model):
    title = models.CharField(max_length=255)
    date = models.DateTimeField(auto_now_add=True)
    content = models.TextField()
    slug = models.SlugField(null=True)
    def __str__(self):
        return self.title

执行数据迁移

python manage.py makemigrations blog
Migrations for 'blog':
  blog/migrations/0002_post_slug.py
    - Add field slug to post

执行显示:

python manage.py migrate blog
Operations to perform:
  Apply all migrations: blog
Running migrations:
  Applying blog.0002_post_slug... OK

此时,数据库已经帮添加了slug列。

1.jpg

然后我们使用命令,进行一次空的迁移。

python manage.py makemigrations blog --empty
Migrations for 'blog':
  blog/migrations/0003_auto_20170926_1105.py


这时,我们打开blog/migrations/0003_auto_20170926_1105.py,内容如下:

from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
    dependencies = [
        ('blog', '0002_post_slug'),
    ]
    operations = [
    ]

然后在这个文件中,我们可以创建一个可以由RunPython命令执行的函数:

blog/migrations/0003_auto_20170926_1105.py

from __future__ import unicode_literals
from django.db import migrations
from django.utils.text import slugify
def slugify_title(apps, schema_editor):
    '''
    We can't import the Post model directly as it may be a newer
    version than this migration expects. We use the historical version.
    '''
    Post = apps.get_model('blog', 'Post')
    for post in Post.objects.all():
        post.slug = slugify(post.title)
        post.save()
class Migration(migrations.Migration):
    dependencies = [
        ('blog', '0002_post_slug'),
    ]
    operations = [
        migrations.RunPython(slugify_title),
    ]

在上面的示例中,我们使用了slugify实用函数。

它将一个字符串作为参数,并将其传入slug.。看下面几个例子:

from django.utils.text import slugify
slugify('Hello, World!')
'hello-world'
slugify('How to Extend the Django User Model')
'how-to-extend-the-django-user-model'

不管怎样,我们运行这个文件的方法来创建数据迁移的函数,还需要两个参数:APPS和schema_editor。运行的时候需要提供。记住,我们还需要从models里导入和使用 apps.get_model('app_name', 'model_name')方法。

保存文件并执行迁移,就像平时我们迁移所做的那样:

python manage.py migrate blog
Operations to perform:
  Apply all migrations: blog
Running migrations:
  Applying blog.0003_auto_20170926_1105... OK

我们再看看我们现在的数据库

1.jpg

每个Post条目都有一个值了,因此我们可以安全地将开关从null=True更改为null=False。由于所有的值都是唯一的,我们还可以添加unique=True标志。

更改模型

blog/models.py

from django.db import models
class Post(models.Model):
    title = models.CharField(max_length=255)
    date = models.DateTimeField(auto_now_add=True)
    content = models.TextField()
    slug = models.SlugField(null=False, unique=True)
    def __str__(self):
        return self.title

创建一个新的迁移

python manage.py makemigrations blog

这时我们将看到以下提示:

You are trying to change the nullable field 'slug' on post to non-nullable without a default; we can't do that
(the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL
 operation to handle NULL values in a previous data migration)
 3) Quit, and let me add a default in models.py
Select an option:

在终端输入“2”,选择选项2

Migrations for 'blog':
  blog/migrations/0004_auto_20170926_1422.py
    - Alter field slug on post

现在我们可以安全地应用迁移了

python manage.py migrate blog
Operations to perform:
  Apply all migrations: blog
Running migrations:
  Applying blog.0004_auto_20170926_1422... OK

最后总结:

数据迁移有时很棘手。在为项目创建数据迁移时,始终首先检查生产数据。我在示例中使用的slugify_title的实现有点不太妥当,因为它可以为大型数据集生成重复的标题。我们在修改数据库的时候,首先在登录环境中首测试数据迁移,以避免在生产环境中破坏东西。

一步一步地去做也很重要,这样你就能感觉到你在控制你所引入的变化。注意,这里我为一个简单的数据迁移创建了三个迁移文件。

如您所见,创建这种类型的迁移非常容易。也很灵活。例如,您可以加载一个外部文本文件,将数据插入到一个新的列中。

本文中使用的源代码可以在上面找到:GitHub:https://github.com/sibtc/data-migrations-example

本文翻译自:https://simpleisbetterthancomplex.com/tutorial/2017/09/26/how-to-create-django-data-migrations.html

文章评论 1

  • 当你的才华和能力还不足以支撑你的野心的时候,静下心来,学习!