Python 在Django中批量创建自动字段
我有两个模型,我想将所有数据从一个迁移到另一个。为了模拟问题,假设以下模型:Python 在Django中批量创建自动字段,python,django,Python,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 BookArc
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\u book.id
),新对象将保留原始时间戳。但数据库中已经存在重叠的ID,所以这种方法将产生另一个问题。如何保存在BookArchived
实例中创建的原始值?我找到了这个应该可以工作的gem,但我自己还没有测试过
从contextlib导入contextmanager
@上下文管理器
def suppress_auto_now(型号、字段名称):
"""
从…起https://stackoverflow.com/a/59898220/519995
这是我的主意https://stackoverflow.com/a/35943149/1731460
"""
字段_state={}
对于字段名称中的字段名称:
field=model.\u meta.get\u字段(字段名称)
fields_state[field]={'auto_now':field.auto_now,'auto_now_add':field.auto_now_add}
对于处于字段状态的字段:
field.auto_now=False
field.auto\u now\u add=False
尝试:
产量
最后:
对于字段,字段中的状态\u state.items():
field.auto\u now=状态['auto\u now']
field.auto\u now\u add=状态['auto\u now\u add']
像这样使用它:
with suppress_autotime(Book, ['created_at']):
Book.objects.bulk_create(book_objs)
注意:
请勿在视图/表单中或Django应用程序中的任何位置使用此上下文管理器。此上下文管理器更改字段的内部状态(通过临时设置auto\u now
和auto\u now\u add
到False
)。这将导致Django在执行并发请求的上下文管理器主体(即同一进程、不同线程)期间,不使用时区.now()填充这些字段。尽管这可用于独立脚本(例如管理命令、数据迁移),但这些脚本与Django应用程序不在同一进程中运行。如日期字段中的注释所示:
按照当前的实现,将auto_now或auto_now_add设置为True
将导致字段设置为可编辑=False和空白=True
由于editable
将设置为False
,因此向模型提供任何值都将无效。您需要在多次迁移中执行此步骤
首先让没有自动的模型现在添加:
class Book(models.Model):
name = models.CharField(max_length=100)
is_archived = models.BooleanField(default=False)
created_at = models.DateTimeField()
使用python manage.py makemigrations
为此生成迁移。接下来我们要做一个测试。首先运行python manage.py makemigrations--empty
这将创建一个空的迁移,您将编辑该迁移以将数据从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\u Now\u 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)
@布隆德格:就是这样。我不明白你为什么需要一个额外的型号。您有一个布尔值来确定书籍是否已存档,因此您可以轻松提取已存档的书籍。@WillemVanOnsem它是旧版模型。现在数据存在于两个不同的表中。这就是为什么我要将所有数据从BookArchived
迁移到Book
,然后删除前一个模型。@ElginCahangirov您需要从Book
模型中删除auto\u now\u add=True
,然后在另一个迁移中重新添加它auto\u now\u add
隐式设置editable=False
,因此您不能自己设置它的值。@AbdulAzizBarkat不适用于生产环境。在部署时,将有对book表的插入(book
model),在这种情况下,此解决方案按预期工作。但我认为,在视图/表单中或项目中的任何地方使用这种方法都是危险的,因为上下文管理器正在改变模型字段的内部状态。此更改将对运行django应用程序的整个进程产生影响,因此在从上下文管理器输入和退出时间之间进行保存时,django不会自动填充字段。你觉得怎么样?我在django视图中测试了这段代码。正如我所设想的,它会导致尝试创建新的Book
实例的并发请求出错,因为suppress\u auto\u now
上下文管理器会在字段更改created\u的状态。因此,在django应用程序中使用此代码并不安全,尽管它符合我的需要。我将在django管理命令中运行迁移代码,该命令将在django应用程序的单独进程中执行。请接受我的编辑,以便将您的答案标记为已接受。您是对的@ElginCahangirov!如果在Web服务器内部使用,这是非常危险的,我只考虑在服务器进程之外运行的数据迁移脚本。-编辑接受,谢谢!谢谢你的努力。提出的解决方案克服了我的问题。但请考虑对应用程序的高并发请求。当我的自定义迁移执行Book时,在处创建的\u将不会是auto\u now\u add
,这将导致服务器错误。@ElginCahangirov这将如何导致错误?在应用所有迁移之前,服务器甚至不应该运行。考虑到运行了python manage.py migrate
,所有这3个迁移都将按顺序应用,之后,如果需要或使用fixture,您可以在另一个数据迁移中添加一些book实例(根据您的注释“部署时将插入book表”)。完成所有迁移并加载初始数据后,理想情况下,服务器应使用python manage.py runserver运行
@ElginCahangirov查看如何为模型提供初始数据。假设数据迁移需要2分钟(或