![](/img/trans.png)
[英]Conditionally bulk_create or bulk_update model instance fields based on the conditional equality of two foreign key related fields in 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_now
和auto_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.