繁体   English   中英

在 Django 中批量创建自动字段

[英]Bulk create auto fields in Django

我有两个模型,我想将所有数据从一个迁移到另一个。 为了模拟问题,假设以下模型:

from django.db import models

class Book:
    name = models.CharField(max_length=100)
    is_archived = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)


class BookArchived:
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)

迁移的代码是:

book_objs = []
for archived_book in BookArchived.objects.all():
    book_objs.append(Book(name=archived_book.name, is_archived=True, created_at=archived_book.created_at))

Book.objects.bulk_create(book_objs)

这段代码的问题在于,尽管我明确设置created_at字段( created_at=archived_book.created_at ),但 Django 将它们全部插入当前时间。

我知道如果pk字段被明确设置( id=archived_book.id ),新对象将保留原始时间戳。 但是数据库中已经有重叠的ID,所以这种方法会产生另一个问题。 如何保留BookArchived实例的原始created_at值?

我发现这个 gem https://stackoverflow.com/a/59898220/519995应该可以工作,但我自己没有测试过。


from contextlib import contextmanager

@contextmanager
def suppress_auto_now(model, field_names):
    """
    From https://stackoverflow.com/a/59898220/519995
    idea taken here https://stackoverflow.com/a/35943149/1731460
    """
    fields_state = {}
    for field_name in field_names:
        field = model._meta.get_field(field_name)
        fields_state[field] = {'auto_now': field.auto_now, 'auto_now_add': field.auto_now_add}

    for field in fields_state:
        field.auto_now = False
        field.auto_now_add = False
    try:
        yield
    finally:
        for field, state in fields_state.items():
            field.auto_now = state['auto_now']
            field.auto_now_add = state['auto_now_add']

像这样使用它:

with suppress_autotime(Book, ['created_at']):
        Book.objects.bulk_create(book_objs)

注意:不要在您的视图/表单或 Django 应用程序的任何地方使用此上下文管理器。 此上下文管理器更改字段的内部 state(通过临时将auto_nowauto_now_add设置为False )。 这将导致 Django 在执行上下文管理器主体的并发请求(即相同的进程,不同的线程)期间不使用timezone.now()填充这些字段。 尽管这可用于独立脚本(例如管理命令、数据迁移),但这些脚本与 Django 应用程序不在同一进程中运行。

根据DateField 文档中的注释:

按照目前的实现,将 auto_now 或 auto_now_add 设置为 True 将导致该字段设置为 editable=False 和 blank=True。

由于editable将设置为False ,因此您向 model 提供任何值都将不起作用。 您将需要在多次迁移中执行此步骤。

首先有没有 auto_now_add 的auto_now_add

class Book(models.Model):
    name = models.CharField(max_length=100)
    is_archived = models.BooleanField(default=False)
    created_at = models.DateTimeField()

使用python manage.py makemigrations为此生成迁移。 接下来我们要做一个数据迁移[Django docs] 首先运行python manage.py makemigrations --empty <yourappname>这将创建一个空迁移,您将对其进行编辑以将数据从BookArchived复制到Book 这看起来像:

from django.db import migrations

def copy_legacy_books(apps, schema_editor):
    # We can't import the Person model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Book = apps.get_model('yourappname', 'Book')
    BookArchived = apps.get_model('yourappname', 'BookArchived')
    book_objs = []
    for archived_book in BookArchived.objects.all():
        book_objs.append(Book(name=archived_book.name, is_archived=True, created_at=archived_book.created_at))
    
    Book.objects.bulk_create(book_objs)

class Migration(migrations.Migration):

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

    operations = [
        migrations.RunPython(copy_legacy_books),
    ]

现在,只需将auto_now_add kwarg 添加到您的日期时间字段并使用python manage.py makemigrations生成另一个迁移:

class Book:
    name = models.CharField(max_length=100)
    is_archived = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM