auto_now / auto_now_addを一時的に無効にする


104

私はこのようなモデルを持っています:

class FooBar(models.Model):
    createtime = models.DateTimeField(auto_now_add=True)
    lastupdatetime = models.DateTimeField(auto_now=True)

一部のモデルインスタンス(データの移行時に使用)の2つの日付フィールドを上書きしたい。現在のソリューションは次のようになります。

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = False
    elif field.name == "createtime":
        field.auto_now_add = False

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = True
    elif field.name == "createtime":
        field.auto_now_add = True

より良い解決策はありますか?


new_entry.createtime.auto_now = False?
akonsu 2011

3
+
1-

@akonsuいいえ: 'datetime.datetime'オブジェクトには属性 'auto_now'がありません
mlissner

2
少数のコアauto_now(_add)
開発者

new_entry._meta.get_field(「date_update」)は、より直接的である
セルジオ

回答:


99

最近、アプリケーションのテスト中にこの状況に直面しました。期限切れのタイムスタンプを「強制」する必要がありました。私の場合、クエリセットの更新を使用してトリックを行いました。このような:

# my model
class FooBar(models.Model):
    title = models.CharField(max_length=255)
    updated_at = models.DateTimeField(auto_now=True, auto_now_add=True)



# my tests
foo = FooBar.objects.get(pk=1)

# force a timestamp
lastweek = datetime.datetime.now() - datetime.timedelta(days=7)
FooBar.objects.filter(pk=foo.pk).update(updated_at=lastweek)

# do the testing.

この回答をありがとうございます。:ここでは、更新()のためのドキュメントですdocs.djangoproject.com/en/dev/ref/models/querysets/...
guettli

1
実際、データベースにアクセスすることを気にしないのであれば、この方法はかなりうまくいきます。私はこれもテストに使用することになりました。
imjustmatthew 2013

3
Djangoドキュメントから:docs.djangoproject.com/en/1.9/topics/db/queries/…update()メソッドはSQLステートメントに直接変換されることに注意してください。これは直接更新の一括操作です。モデルでsave()メソッドを実行したり、pre_saveまたはpost_saveシグナル(save()を呼び出した結果)を発行したり、auto_nowフィールドオプションを受け入れたりしない
NoamG

@NoamGこれは、このupdate()振る舞いがまさに私たちが必要とするものであるまれなケースだと思います。
David D.

54

auto_now / auto_now_addを、すでに行っている以外の方法で実際に無効にすることはできません。これらの値を変更する柔軟性が必要な場合、auto_now/ auto_now_addは最適な選択ではありません。多くの場合、オブジェクトが保存される直前にメソッドを使用defaultおよび/またはオーバーライドsave()して操作を行う方が柔軟性があります。

defaultオーバーライドされたsave()メソッドを使用して問題を解決する1つの方法は、次のようにモデルを定義することです。

class FooBar(models.Model):
    createtime = models.DateTimeField(default=datetime.datetime.now)
    lastupdatetime = models.DateTimeField()

    def save(self, *args, **kwargs):
        if not kwargs.pop('skip_lastupdatetime', False):
            self.lastupdatetime = datetime.datetime.now()

        super(FooBar, self).save(*args, **kwargs)

コードで、lastupdatetimeの自動変更をスキップする場合は、

new_entry.save(skip_lastupdatetime=True)

オブジェクトが管理インターフェースまたは他の場所に保存されている場合、save()はskip_lastupdatetime引数なしで呼び出され、以前と同じように動作しauto_nowます。


14
TL; DR 代わりにuseをauto_now_add使用しないでくださいdefault
Thane Brimhall、2015

9
この例の注意点の1つdatetime.datetime.nowは、単純な日時を返すことです。タイムゾーンに対応した日時を使用するfrom django.utils import timezonemodels.DateTimeField(default=timezone.now)は、docs.djangoproject.com
BenjaminGolder

したがって、明確にするために、createtimeフィールドを変更できるようにするだけの場合は、をオーバーライドする必要はありませんsave()。(docsによると)auto_now_add=True同等のもので置き換えるだけで十分です。後者の2つのオプションは、管理者で同様の動作を保証します。default=timezone.now, editable=False, blank=True
djvg

28

質問者の提案を利用して、いくつかの関数を作成しました。これがユースケースです:

turn_off_auto_now(FooBar, "lastupdatetime")
turn_off_auto_now_add(FooBar, "createtime")

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()

ここに実装があります:

def turn_off_auto_now(ModelClass, field_name):
    def auto_now_off(field):
        field.auto_now = False
    do_to_model(ModelClass, field_name, auto_now_off)

def turn_off_auto_now_add(ModelClass, field_name):
    def auto_now_add_off(field):
        field.auto_now_add = False
    do_to_model(ModelClass, field_name, auto_now_add_off)

def do_to_model(ModelClass, field_name, func):
    field = ModelClass._meta.get_field_by_name(field_name)[0]
    func(field)

同様の関数を作成して、それらをオンに戻すことができます。


9
ほとんどの場合、反復の代わりに、おそらく単に行うことができますClazz._meta.get_field_by_name(field_name)[0]
naktinis 2013年

@naktinisに感謝します。かわった。
フランクヘナード2016年

4
nb(1.10)do_to_modelのModelClass._meta.get_field_by_name(field_name)[0]が機能していないようです-次のように変更されました:ModelClass._meta.get_field(field_name)
Georgina S

27

update_fieldsパラメータを使用しsave()auto_nowフィールドを渡すこともできます。次に例を示します。

# Date you want to force
new_created_date = date(year=2019, month=1, day=1)
# The `created` field is `auto_now` in your model
instance.created = new_created_date
instance.save(update_fields=['created'])

Djangoのドキュメントの説明は次のとおりです。https//docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save


1
私はこれをもっと高く投票できればいいのに!これは本当に私が欲しかったこと(「自動」フィールドに触れずにモデルの一部を変更するため)ですが、残念ながら、与えられた質問には答えません(これは、auto_nowおよびを使用してフィールドに明示的な値を保存することですauto_now_add)。
Tim Tisdall 2017

1
ベストアンサー、私はそれに非常にシンプルでエレガントな10票を与えることができるといいのですが
Bedros

18

私は再利用のためにコンテキストマネージャーの方法を採用しました。

@contextlib.contextmanager
def suppress_autotime(model, fields):
    _original_values = {}
    for field in model._meta.local_fields:
        if field.name in fields:
            _original_values[field.name] = {
                'auto_now': field.auto_now,
                'auto_now_add': field.auto_now_add,
            }
            field.auto_now = False
            field.auto_now_add = False
    try:
        yield
    finally:
        for field in model._meta.local_fields:
            if field.name in fields:
                field.auto_now = _original_values[field.name]['auto_now']
                field.auto_now_add = _original_values[field.name]['auto_now_add']

そのように使用してください:

with suppress_autotime(my_object, ['updated']):
    my_object.some_field = some_value
    my_object.save()

ブーム。


8

テストを書いているときにこれを見ている人のために、freezegunと呼ばれるpythonライブラリがあります。これにより、時間を偽ることができます。そのため、auto_now_addコードを実行すると、実際に必要な時間が得られます。そう:

from datetime import datetime, timedelta
from freezegun import freeze_time

with freeze_time('2016-10-10'):
    new_entry = FooBar.objects.create(...)
with freeze_time('2016-10-17'):
    # use new_entry as you wish, as though it was created 7 days ago

また、デコレータとしても使用できます。基本的なドキュメントについては、上記のリンクを参照してください。


4

auto_now_add特別なコードなしでオーバーライドできます。

特定の日付でオブジェクトを作成しようとしたときにこの質問に遭遇しました:

Post.objects.create(publication_date=date, ...)

どこpublication_date = models.DateField(auto_now_add=True)

これが私がやったことです:

post = Post.objects.create(...)
post.publication_date = date
post.save()

これは正常に上書きされましたauto_now_add

より長期的な解決策として、saveメソッドをオーバーライドすることをお勧めします:https : //code.djangoproject.com/ticket/16583


2

移行中にDateTimeフィールドのauto_nowを無効にする必要があり、これを行うことができました。

events = Events.objects.all()
for event in events:
    for field in event._meta.fields:
        if field.name == 'created_date':
            field.auto_now = False
    event.save()

1

私はパーティーに遅れましたが、他のいくつかの回答と同様に、これはデータベースの移行中に使用したソリューションです。他の回答との違いは、このようなフィールドが複数ある理由は実際にはないという前提の下で、モデルのすべての auto_nowフィールドを無効にすることです。

def disable_auto_now_fields(*models):
    """Turns off the auto_now and auto_now_add attributes on a Model's fields,
    so that an instance of the Model can be saved with a custom value.
    """
    for model in models:
        for field in model._meta.local_fields:
            if hasattr(field, 'auto_now'):
                field.auto_now = False
            if hasattr(field, 'auto_now_add'):
                field.auto_now_add = False

次に、それを使用するために、あなたは単に行うことができます:

disable_auto_now_fields(Document, Event, ...)

そして、渡されたすべてのモデルクラスのauto_nowauto_now_addフィールドのすべてを通過して核にします。


1

https://stackoverflow.com/a/35943149/1731460のコンテキストマネージャのもう少しクリーンなバージョン

from contextlib import contextmanager

@contextmanager
def suppress_auto_now(model, field_names):
    """
    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']

工場(factory-boy)でも使用できます

        with suppress_autotime(Click, ['created']):
            ClickFactory.bulk_create(post=obj.post, link=obj.link, created__iter=created)

0

Djangoのコピー-Models.DateTimeField-auto_now_add値を動的に変更

さて、私は今日の午後の調査に費やしました。最初の問題は、モデルオブジェクトをフェッチする方法とコードのどこにあるかです。私はserializer.pyでrestframeworkにいます、たとえば__init__シリアライザではまだモデルを持っていません。これで、to_internal_valueで、フィールドを取得した後、次の例のようにフィールドプロパティを変更した後、モデルクラスを取得できます。

class ProblemSerializer(serializers.ModelSerializer):

    def to_internal_value(self, data): 
        ModelClass = self.Meta.model
        dfil = ModelClass._meta.get_field('date_update')
        dfil.auto_now = False
        dfil.editable = True

0

で動作するソリューションが必要でしたがupdate_or_create、@ andreaspelmeコードに基づいてこのソリューションにたどり着きました。

唯一の変更点は、skipkwarg skip_modified_updateをsave()メソッドに渡すだけでなく、変更されたフィールドを設定することでスキップを設定できることです。

ただyourmodelobject.modified='skip'、更新はスキップされます!

from django.db import models
from django.utils import timezone


class TimeTrackableAbstractModel(models.Model):
    created = models.DateTimeField(default=timezone.now, db_index=True)
    modified = models.DateTimeField(default=timezone.now, db_index=True)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        skip_modified_update = kwargs.pop('skip_modified_update', False)
        if skip_modified_update or self.modified == 'skip':
            self.modified = models.F('modified')
        else:
            self.modified = timezone.now()
        super(TimeTrackableAbstractModel, self).save(*args, **kwargs)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.