簡體   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