我有一个这样的模型:
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
有更好的解决方案吗?
答案 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
首次创建对象时自动将字段设置为现在。用于创建时间戳。请注意,始终使用当前日期; 这不仅仅是您可以覆盖的默认值。所以即使你在创建对象时为这个字段设置了一个值,它也会被忽略。 如果您希望能够修改此字段,请设置以下内容而不是 auto_now_add=True
:
对于DateField:default=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_now
和auto_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