![](/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.