暂时禁用 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

还有更好的解决办法吗?

35883 次浏览

您不能以另一种方式禁用 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)

如果对象保存在管理接口或其他位置,则调用 save ()时将不使用 Skip _ lastatdatetime 参数,它的行为将与以前使用 auto_now时一样。

我使用了询问者提出的建议,并创建了一些函数:

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)

可以创建类似的函数来重新打开它们。

我最近在测试我的应用程序时遇到了这种情况。我需要“强制”一个过期的时间戳。在我的例子中,我使用了一个查询集更新。像这样:

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

在迁移期间,我需要为一个 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()

我来晚了,但与其他几个答案类似,这是我在数据库迁移期间使用的一个解决方案。与其他答案的不同之处在于,这样做禁用了模型的 所有 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字段。

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

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

砰。

对于那些在编写测试时关注这个问题的人来说,有一个名为 冷冻枪的 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

它也可以作为一个装饰-参见上面基本文档的链接。

您还可以使用 save()update_fields参数并传递 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

Django-Models.DateTimeField-动态更改 auto _ now _ add 值的副本

我花了一个下午来找出第一个问题是如何获取模型对象以及代码的位置。我现在处于 seralizer.py 中的 restFramework 中,例如在序列化程序的 __init__中,它还不能拥有 Model。现在,在 to _ inside _ 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

我需要的解决方案,将与 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)

不需要特殊代码就可以覆盖 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

https://stackoverflow.com/a/35943149/1731460中上下文管理器的一个更干净的版本

注意: 不要在视图/表单或 Django 应用程序的任何地方使用这个上下文管理器。 这个上下文管理器改变字段的内部状态(通过临时设置 auto _ now 和 auto _ now _ add 为 False)。 这将导致 Django 在执行上下文期间不使用 timezone.now ()填充这些字段 并发请求(即同一进程,不同线程)的管理器主体。

尽管这可以用于独立的脚本(例如管理命令、数据迁移) 这是 不在同一进程中运行与 Django 应用程序。

from contextlib import contextmanager


@contextmanager
def suppress_auto_now(model, field_names=None):
"""
Temp disable auto_now and auto_now_add for django fields
@model - model class or instance
@field_names - list of field names to suppress or all model's
fields that support auto_now_add, auto_now"""


def get_auto_now_fields(user_selected_fields):
for field in model._meta.get_fields():
field_name = field.name
if user_selected_fields and field_name not in user_selected_fields:
continue
if hasattr(field, 'auto_now') or hasattr(field, 'auto_now_add'):
yield field


fields_state = {}


for field in get_auto_now_fields(user_selected_fields=field_names):
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_auto_now(Click, ['created']):
ClickFactory.bulk_create(post=obj.post, link=obj.link, created__iter=created)

来自姜戈 医生

Auto _ now _ add

自动将字段设置为首次创建对象的时间。对于创建时间戳非常有用。请注意,始终使用当前日期; 它不仅仅是一个可以重写的默认值。因此,即使在创建对象时为该字段设置了值,也将忽略该值。如果希望能够修改此字段,请将 auto_now_add=True设置为以下值:

对于 日期栏: default=date.today-from datetime.date.today ()

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

这里是@soulSeekah https://stackoverflow.com/a/35943149/202168中有用答案的另一个变体和简化

这种方法可以同时抑制多个模型上的字段-与 factory_boy结合使用时非常有用,比如当您有一个子工厂(SubFactory)时,该子工厂也有需要抑制的字段

看起来像是:

@contextmanager
def suppress_autonow(*fields: DeferredAttribute):
_original_values = {}
for deferred_attr in fields:
field = deferred_attr.field
_original_values[field] = {
'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, values in _original_values.items():
field.auto_now = values['auto_now']
field.auto_now_add = values['auto_now_add']

用法如下(与 factory_boy一起使用) :

with suppress_autonow(Comment.created_at, Post.created_at):
PostFactory.create_batch(10)  # if e.g. PostFactory also creates Comments

或者只是姜戈:

with suppress_autonow(FooBar.createtime, FooBar.lastupdatetime):
foobar = FooBar(
createtime=datetime(2013, 4, 6),
lastupdatetime=datetime(2016, 7, 9),
)
foobar.save()

也就是说,传入要禁止显示的实际字段。

请注意,您必须将它们作为类字段(即 Comment.created_at)而不是实例字段(而不是 my_comment.created_at)传递