it-swarm.cn

暂时禁用auto_now/auto_now_add

我有这样的模型:

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

有更好的解决方案吗?

85
jedie

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

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

你不能以另一种方式禁用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之前的行为相同。

50
andreaspelme

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

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)

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

22
Frank Henard

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

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

繁荣。

16
soulseekah

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

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

15
Apollo Data

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

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

7
Hamish Downer

我需要在迁移期间为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()
2
user1172023

我迟到了,但与其他几个答案类似,这是我在数据库迁移过程中使用的解决方案。与其他答案的不同之处在于,在假设没有理由拥有多个这样的字段的情况下,这会禁用模型的 all 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
mlissner

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

只有通过实际将kwarg skip_modified_update传递给save()方法,您可以通过将修改后的字段设置为skip来设置跳过。

只是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)
0
lechup

副本 Django - Models.DateTimeField - 动态更改auto_now_add值

好吧,我今天下午花了很多钱找出来,第一个问题是如何获取模型对象以及代码中的位置。我在serializer.py的restframework中,例如在序列化器的__init__中,它还没有Model。现在在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
0
Sérgio

您可以在没有特殊代码的情况下覆盖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

0
Pavel Vergeev