暂时停用auto_now / auto_now_add


104

我有一个像这样的模型:

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

有更好的解决方案吗?


new_entry.createtime.auto_now =假?
akonsu 2011年

3
+
1-

@akonsu Nope:“ datetime.datetime”对象没有属性“ auto_now”
mlissner,2013年

2
值得指出的是,有许多核心开发人员赞成弃用auto_now(_add)
mlissner 2015年

new_entry._meta.get_field('date_update')更直接
Sérgio)

Answers:


99

我最近在测试我的应用程序时遇到了这种情况。我需要“强制”过期的时间戳。就我而言,我通过使用queryset更新来达到目的。像这样:

# 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

3
从Django文档中获取:docs.djangoproject.com/en/1.9/topics/db/queries/…请注意,update()方法直接转换为SQL语句。这是直接更新的批量操作。它不会在模型上运行任何save()方法,也不会发出pre_save或post_save信号(这是调用save()的结果),也不会遵守auto_now字段选项
NoamG '16

@NoamG我​​认为这种情况很少发生,这update()正是我们所需要的。
David D.19年

54

您不能以除已执行的方式之外的其他方式来真正禁用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


14
TL; DR不要使用auto_now_adduse default来代替。
塔娜·布里姆霍尔

9
此示例的一个警告是datetime.datetime.now返回原始日期时间。要使用可识别时区的日期时间,请使用from django.utils import timezonemodels.DateTimeField(default=timezone.now)参阅docs.djangoproject.com/en/1.9/topics/i18n/timezones/…–
BenjaminGolder

因此,请注意:如果只希望能够修改createtime字段,则无需覆盖save()auto_now_add=True用等效项替换就足够了default=timezone.now, editable=False, blank=True(根据docs)。后两个选项可确保在管理员中具有相似的行为。
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

谢谢@naktinis。变了
弗兰克·汉纳德

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)。
蒂姆·迪斯达尔

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']

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

        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__serializer中还没有模型。现在,在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

我需要可以使用的解决方案 update_or_create,我已经基于@andreaspelme代码找到了该解决方案。

唯一的变化是,您可以通过将修改字段设置为skip不仅可以通过实际传递kwarg 来设置跳过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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.