Django auto_now 和 auto_now_add
- 2025-02-14 09:49:00
- admin 原创
- 46
问题描述:
对于 Django 1.1。
我的 models.py 中有这个:
class User(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
更新一行时我得到:
[Sun Nov 15 02:18:12 2009] [error] /home/ptarjan/projects/twitter-meme/django/db/backends/mysql/base.py:84: Warning: Column 'created' cannot be null
[Sun Nov 15 02:18:12 2009] [error] return self.cursor.execute(query, args)
我的数据库的相关部分是:
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
这是否值得担忧?
附带问题:在我的管理工具中,这两个字段没有显示。这是预料之中的吗?
解决方案 1:
任何具有该auto_now
属性集的字段也将继承editable=False
,因此不会显示在管理面板中。过去曾有人讨论过让auto_now
和auto_now_add
参数消失,尽管它们仍然存在,但我觉得你最好只使用自定义save()
方法。
因此,为了使其正常工作,我建议不要使用auto_now
或,auto_now_add
而是定义自己的save()
方法,以确保created
仅在未设置时更新id
(例如首次创建项目时),并在modified
每次保存项目时更新它。
我对使用 Django 编写的其他项目做了完全相同的事情,因此你save()
看起来会像这样:
from django.utils import timezone
class User(models.Model):
created = models.DateTimeField(editable=False)
modified = models.DateTimeField()
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
self.created = timezone.now()
self.modified = timezone.now()
return super(User, self).save(*args, **kwargs)
根据评论进行编辑:
我坚持重载save()
而不是依赖这些字段参数的原因有两个:
上述可靠性方面存在着优缺点。这些论点在很大程度上依赖于 Django 知道如何与之交互的每种类型的数据库处理日期/时间戳字段的方式,并且似乎在每次发布之间都会中断和/或更改。(我认为这是呼吁将它们全部删除的动力)。
事实上,它们只适用于 DateField、DateTimeField 和 TimeField,并且通过使用此技术,您可以在每次保存项目时自动填充任何字段类型。
使用
django.utils.timezone.now()
vs.datetime.datetime.now()
,因为它将datetime.datetime
根据 返回 TZ 感知或简单对象settings.USE_TZ
。
至于为什么 OP 看到错误,我不太清楚,但看起来好像created
根本没有被填充,尽管有auto_now_add=True
。对我来说,这是一个错误,并强调了我上面小列表中的第 1 项: auto_now
并且auto_now_add
充其量是不稳定的。
解决方案 2:
但我想指出的是,接受的答案中表达的观点有些过时了。根据最近的讨论(django bug #7634和#12785),auto_now
并且auto_now_add
没有任何进展,即使你去原始讨论,你也会发现反对自定义保存方法中的 RY(如 DRY)的强烈论据。
已经提出了一个更好的解决方案(自定义字段类型),但没有足够的动力将其纳入django。您可以用三行编写自己的代码(这是Jacob Kaplan-Moss的建议)。
from django.db import models
from django.utils import timezone
class AutoDateTimeField(models.DateTimeField):
def pre_save(self, model_instance, add):
return timezone.now()
#usage
created_at = models.DateField(default=timezone.now)
updated_at = AutoDateTimeField(default=timezone.now)
解决方案 3:
谈论一个附带问题:如果您想在管理员中看到这些字段(但是,您将无法编辑它),您可以将其添加readonly_fields
到您的管理员类别中。
class SomeAdmin(ModelAdmin):
readonly_fields = ("created","modified",)
嗯,这仅适用于最新的 Django 版本(我相信 1.3 及以上版本)
解决方案 4:
我认为这里最简单(也可能是最优雅)的解决方案是利用您可以设置为可调用的事实default
。因此,为了绕过管理员对 auto_now 的特殊处理,您可以像这样声明该字段:
from django.utils import timezone
date_field = models.DateField(default=timezone.now)
重要的是不要使用timezone.now()
默认值,因为默认值不会更新(即,默认值仅在加载代码时设置)。如果你发现自己经常这样做,你可以创建一个自定义字段。然而,我认为这已经很枯燥了。
解决方案 5:
class Feedback(models.Model):
feedback = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
在这里,我们创建并更新了列,并在创建时以及有人修改反馈时带有时间戳。
auto_now_add
将设置创建实例的时间,而auto_now
将设置某人修改其反馈的时间。
解决方案 6:
如果你像这样改变模型类:
class MyModel(models.Model):
time = models.DateTimeField(auto_now_add=True)
time.editable = True
然后这个字段就会出现在我的管理更改页面中
解决方案 7:
根据我读过的内容以及迄今为止使用 Django 的经验,auto_now_add 存在错误。我同意 jthanism --- 覆盖正常的保存方法,这样就很干净,而且你知道发生了什么。现在,为了使其变得简单,请创建一个名为 TimeStamped 的抽象模型:
from django.utils import timezone
class TimeStamped(models.Model):
creation_date = models.DateTimeField(editable=False)
last_modified = models.DateTimeField(editable=False)
def save(self, *args, **kwargs):
if not self.creation_date:
self.creation_date = timezone.now()
self.last_modified = timezone.now()
return super(TimeStamped, self).save(*args, **kwargs)
class Meta:
abstract = True
然后,当您想要一个具有此时间戳行为的模型时,只需进行子类化:
MyNewTimeStampyModel(TimeStamped):
field1 = ...
如果您希望字段显示在管理中,则只需删除该editable=False
选项
解决方案 8:
这是否值得担忧?
不,Django 在保存模型时会自动为您添加它,所以这是预料之中的。
附带问题:在我的管理工具中,这两个字段没有显示。这是预料之中的吗?
由于这些字段是自动添加的,因此不会显示。
补充一下,正如 synack 所说,django 邮件列表中曾有一场关于删除该功能的争论,因为它“设计得不好”并且是“黑客行为”
在我的每个模型上编写自定义 save() 比使用 auto_now 要痛苦得多
显然你不必将它写入每个模型。你可以将它写入一个模型并从中继承其他模型。
但是,由于auto_add
和auto_now_add
存在,我会使用它们,而不是尝试自己编写方法。
解决方案 9:
至于您的管理显示,请参阅此答案。
注意:auto_now
和默认auto_now_add
设置为editable=False
,这就是适用的原因。
解决方案 10:
我今天在工作中需要类似的东西。默认值为timezone.now()
,但在管理视图和继承自的类视图中均可编辑FormMixin
,因此在我的models.py
以下代码中创建了满足这些要求的代码:
from __future__ import unicode_literals
import datetime
from django.db import models
from django.utils.functional import lazy
from django.utils.timezone import localtime, now
def get_timezone_aware_now_date():
return localtime(now()).date()
class TestDate(models.Model):
created = models.DateField(default=lazy(
get_timezone_aware_now_date, datetime.date)()
)
对于,我猜从函数中DateTimeField
删除并更改为或更好。我还没有尝试过,只尝试过。.date()
`datetime.datedatetime.datetime
timezone.datetimeDateTime
Date`
解决方案 11:
auto_now=True
在 Django 1.4.1 中对我来说不起作用,但下面的代码拯救了我。它用于时区感知日期时间。
from django.utils.timezone import get_current_timezone
from datetime import datetime
class EntryVote(models.Model):
voted_on = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
self.voted_on = datetime.now().replace(tzinfo=get_current_timezone())
super(EntryVote, self).save(*args, **kwargs)
解决方案 12:
您可以使用timezone.now()
创建和auto_now
修改:
from django.utils import timezone
class User(models.Model):
created = models.DateTimeField(default=timezone.now())
modified = models.DateTimeField(auto_now=True)
如果您使用自定义主键而不是默认主键auto- increment int
,auto_now_add
将导致错误。
以下是 Django 默认的DateTimeField.pre_save的代码,其中包含auto_now
和auto_now_add
:
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = timezone.now()
setattr(model_instance, self.attname, value)
return value
else:
return super(DateTimeField, self).pre_save(model_instance, add)
我不确定参数add
是什么。我希望它是类似这样的:
add = True if getattr(model_instance, 'id') else False
新记录将没有 attr
id
,因此getattr(model_instance, 'id')
将返回 False ,导致不会在字段中设置任何值。
解决方案 13:
您可以使用此代码技术来创建 [创建日期](在创建行时自动保存日期并且从不更新每次更新的日期和时间)和 [更新日期](每次更新行时更改日期和时间)。
from django.db import models
from django.utils.timezone import now
class Artical(models.Model):
creation = models.DateTimeField(null=True, default=None, blank=True)
updation = models.DateTimeField(null=True,default=None,blank=True)
def save(self, *args, **kwargs):
if not self.creation:
self.creation = now()
self.updation = now()
super(Artical, self).save(*args, **kwargs)
解决方案 14:
关于 update_fields 已经有一些评论,但它没有包含在答案中,所以我在这里做了一个重要的修订。默认情况下,当 django 保存一个实例时,它会保存该实例中的每个字段。当只有一个字段被更新时,这是浪费的,更经济的方法是只保存被更改的字段。这是在 django 中使用update_fields参数完成的,如下所示:
qt = QueuedTask.objects.create()
qt.status = PENDING
qt.save(update_fields=['status'])
现在您的保存方法应该考虑到这一点,否则在上面的保存中,last_modified字段也不会被保存(请注意,我不确定是否创建,但是因为您可以创建一个新实例,并使用带有更新字段的保存方法,所以我也在保存中包含了条件):
def save(self, *args, **kwargs):
if self.id is None:
self.created_on = timezone.now()
update_fields = kwargs.get('update_fields')
if update_fields is not None and 'created_on' not in update_fields:
update_fields += ['created_on']
else:
self.last_modified = timezone.now()
update_fields = kwargs.get('update_fields')
if update_fields is not None and 'last_modified' not in update_fields:
update_fields += ['last_modified']
return super(QueuedTask, self).save(*args, **kwargs)
解决方案 15:
如文档中所述:
使用 update() 还可以防止出现竞争条件,即在加载对象和调用 save() 之间的短时间内数据库中可能会发生某些变化。最后,请注意 update() 在 SQL 级别执行更新,因此不会调用模型上的任何 save() 方法,也不会发出 pre_save 或 post_save 信号(这是调用 Model.save() 的结果)。如果要更新具有自定义 save() 方法的模型的一组记录,请循环遍历它们并调用 save(),如下所示:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()