暂时禁用auto_now / auto_now_add

时间:2011-09-21 12:31:45

标签: django datetime

我有一个这样的模型:

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

我想覆盖某些模型实例的两个日期字段(在迁移数据时使用)。目前的解决方案如下:

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

有更好的解决方案吗?

13 个答案:

答案 0 :(得分:86)

我最近在测试我的应用程序时遇到了这种情况。我需要“强制”过期的时间戳。在我的情况下,我通过使用查询集更新来完成这个技巧。像这样:

# 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.

答案 1 :(得分:50)

你不能以另一种方式禁用auto_now / auto_now_add。如果您需要灵活地更改这些值,auto_now / auto_now_add不是最佳选择。在保存对象之前,使用default和/或覆盖save()方法进行操作通常会更灵活。

使用default和重写save()方法,解决问题的一种方法是定义您的模型:

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)

如果您的对象保存在管理界面或其他地方,则会在没有skip_lastupdatetime参数的情况下调用save(),并且它的行为与之前使用auto_now的行为相同。

答案 2 :(得分:22)

我使用了提问者提出的建议,并创建了一些功能。以下是用例:

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)

可以创建类似的功能来重新打开它们。

答案 3 :(得分:15)

我采用了上下文管理器的方式来实现可重用性。

@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()

动臂。

答案 4 :(得分:15)

如果你知道要限制哪些字段并排除auto_now / auto_now_add字段,你也可以使用update_fields参数save()

https://docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save

答案 5 :(得分:7)

对于那些在编写测试时看到这个的人,有一个名为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

它也可以用作装饰器 - 请参阅上面的基本文档链接。

答案 6 :(得分: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()

答案 7 :(得分:2)

来自 Django docs

DateField.auto_now_add

首次创建对象时自动将字段设置为现在。用于创建时间戳。请注意,始终使用当前日期; 这不仅仅是您可以覆盖的默认值。所以即使你在创建对象时为这个字段设置了一个值,它也会被忽略。 如果您希望能够修改此字段,请设置以下内容而不是 auto_now_add=True

对于DateFielddefault=date.today - from datetime.date.today()

对于 DateTimeField:default=timezone.now - 来自 django.utils.timezone.now()

答案 8 :(得分: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字段。

答案 9 :(得分: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']

您甚至可以与工厂(工厂男孩)一起使用它

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

答案 10 :(得分:0)

Django - Models.DateTimeField - Changing dynamically auto_now_add value

的副本 嗯,我今天下午花了很多时间找出来,第一个问题是如何获取模型对象以及代码中的位置。我在serializer.py中的restframework中,例如在__init__的序列化程序中,它还没有模型。现在在to_internal_value中,您可以在获取Field之后获取模型类,然后修改字段属性,如下例所示:

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

答案 11 :(得分:0)

我需要可以与update_or_create一起使用的解决方案,我已经基于@andreaspelme代码来到了这个解决方案。

唯一的变化是,不仅可以通过将kwarg skip实际传递给save()方法,还可以通过将修改字段设置为skip_modified_update来设置跳过。

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)

答案 12 :(得分:0)

您可以覆盖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