暂时禁用auto_now/auto_now_add

jed*_*die 91 django datetime

我有这样的模型:

class FooBar(models.Model):
    createtime = models.DateTimeField(auto_now_add=True)
    lastupdatetime = models.DateTimeField(auto_now=True)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

有更好的解决方案吗?

dir*_*rls 91

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

# 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.
Run Code Online (Sandbox Code Playgroud)

  • 来自Django文档:https://docs.djangoproject.com/en/1.9/topics/db/queries/#updating-multiple-objects-at-once请注意,update()方法直接转换为SQL语句.这是直接更新的批量操作.它不会在模型上运行任何save()方法,或者发出pre_save或post_save信号(这是调用save()的结果),或者遵循auto_now字段选项 (2认同)
  • @NoamG 我认为这是一种罕见的情况,这种“update()”行为正是我们所需要的。 (2认同)

and*_*lme 52

你不能以另一种方式禁用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)
Run Code Online (Sandbox Code Playgroud)

在您的代码中,您想跳过自动lastupdatetime更改,只需使用

new_entry.save(skip_lastupdatetime=True)
Run Code Online (Sandbox Code Playgroud)

如果您的对象保存在管理界面或其他位置,则会在没有skip_lastupdatetime参数的情况下调用save(),并且它将像以前一样运行auto_now.

  • TL; DR不要使用`auto_now_add`来改为使用`default`. (13认同)
  • 一个需要注意这个例子是`datetime.datetime.now`返回一个天真的日期时间.要使用时区感知日期时间,使用`从django.utils导入timezone`和`models.DateTimeField(默认值= timezone.now)`看https://docs.djangoproject.com/en/1.9/topics/i18n/timezones /#幼稚和感知,日期时间,对象 (9认同)

Fra*_*ard 25

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

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

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()
Run Code Online (Sandbox Code Playgroud)

这是实施:

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)
Run Code Online (Sandbox Code Playgroud)

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

  • 在大多数情况下,您可能只需执行`Clazz._meta.get_field_by_name(field_name)[0]`而不是迭代. (9认同)
  • nb(1.10)do_to_model中的ModelClass._meta.get_field_by_name(field_name)[0]似乎对我不起作用 - 更改为:ModelClass._meta.get_field(field_name) (3认同)

Apo*_*ata 19

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

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

  • 我希望我能投更高的票!这确实是我想要的(在不触及“auto”字段的情况下更改模型的部分内容),但不幸的是没有回答给出的问题(即使用“auto_now”和“auto_now_add”将显式值保存到字段中) 。 (2认同)

sou*_*kah 17

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

@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']
Run Code Online (Sandbox Code Playgroud)

使用如下:

with suppress_autotime(my_object, ['updated']):
    my_object.some_field = some_value
    my_object.save()
Run Code Online (Sandbox Code Playgroud)

繁荣.


Ham*_*ner 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
Run Code Online (Sandbox Code Playgroud)

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


Pav*_*eev 5

auto_now_add无需特殊代码即可覆盖。

当我尝试创建具有特定日期的对象时遇到了这个问题:

Post.objects.create(publication_date=date, ...)
Run Code Online (Sandbox Code Playgroud)

哪里publication_date = models.DateField(auto_now_add=True)

所以这就是我所做的:

post = Post.objects.create(...)
post.publication_date = date
post.save()
Run Code Online (Sandbox Code Playgroud)

这已成功覆盖auto_now_add.

作为一个更长期的解决方案,覆盖save方法是要走的路:https : //code.djangoproject.com/ticket/16583