Django auto_now和auto_now_add


272

对于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,

这值得关注吗?

附带问题:在我的管理工具中,这两个字段没有显示。那是预期的吗?


3
您使用的是自定义主键,而不是默认的自动增量int吗?我发现使用自定义主键会导致此问题。无论如何,我想您已经解决了。但是该错误仍然存​​在。Just my 0.02 $
Tapan 2011年

3
提醒一下。update()方法不会调用save(),这意味着它无法modified自动更新字段
Chemical Programmer

Answers:


383

auto_now设置了属性的任何字段也会继承editable=False,因此不会显示在管理面板中。过去有过关于使auto_nowand auto_now_add参数消失的讨论,尽管它们仍然存在,但我觉得您最好只使用自定义save()方法

因此,为了使其正常工作,我建议不要使用auto_nowauto_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()与依赖这些字段参数的原因有两个:

  1. 前述的起伏具有其可靠性。这些参数在很大程度上取决于Django知道如何与之交互的每种类型的数据库对待日期/时间戳字段的方式,并且似乎在每个发行版之间都会中断和/或更改。(我相信这是彻底删除它们的呼吁的推动力)。
  2. 它们仅在DateField,DateTimeField和TimeField上起作用,使用这种技术,您可以在每次保存项目时自动填充任何字段类型。
  3. 使用django.utils.timezone.now()vs. datetime.datetime.now(),因为它会根据来返回可感知TZ或天真的datetime.datetime对象settings.USE_TZ

为了解决OP为何看到该错误的原因,我不完全知道,但created尽管有,但看起来根本没有被填充auto_now_add=True。对我来说,它是一个bug,并且在我上面的小列表中强调了项目#1: auto_now并且auto_now_add充其量是片状的。


9
但是作者问题的根源是什么?auto_now_add有时会无法正常工作吗?
德米特里·里森贝格

5
我和你在一起德米特里。我很好奇为什么这两个字段会引发错误。我甚至更好奇为什么您认为编写自己的自定义save()方法更好?
霍拉

45
save()与使用模型相比,在每个模型上编写自定义控件要痛苦得多auto_now(因为我希望在所有模型上都包含这些字段)。这些参数为什么不起作用?
Paul Tarjan

3
@TM,但这需要直接摆弄您的数据库,而Django则只希望使用models.py文件来定义架构
akaihola 2011年

11
我非常不同意。1)editable = False是正确的,您不应该编辑该字段,您的数据库需要准确。2)在各种各样的极端情况下,可能不会调用save(),特别是在自定义SQL更新或正在使用的任何情况下。3)这是数据库实际上最擅长的,还有引用完整性等等。信任数据库以使其正确无误是一个很好的默认选择,因为比您(或我)更聪明的头脑将数据库设计为以这种方式工作。
Shayne

175

但是我想指出的是,已接受答案中表达的观点有些过时。根据最近的讨论(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 = models.AutoDateTimeField(default=timezone.now)

1
三行自定义字段在此处:链接
hgcrpd

考虑到您可以将默认值设置为可调用项(即timezone.now),我认为自定义字段不是真正必要的。请参阅下面的答案。
2013年

6
这与auto_add在Django中所做的相同,自2010年以来一直存在:github.com/django/django/blob/1.8.4/django/db/models/fields/…。除非在pre_save中需要其他挂钩,否则我会坚持使用auto_add。
jwhitlock

1
在Django 1.9上不适用于我,因此此解决方案不适用于所有地方,因为auto_now *从来没有。在每种使用情况下(甚至对于'update_fields'arg问题)都可行的唯一解决方案是覆盖保存
danius

4
为什么将默认值设置为timezone.now,但是pre_save信号正在使用datetime.datetime.now?
Bobort

32

谈论一个附带的问题:如果您想在admin中查看此字段(尽管您将无法对其进行编辑),则可以将其添加readonly_fields到admin类中。

class SomeAdmin(ModelAdmin):
    readonly_fields = ("created","modified",)

好吧,这仅适用于最新的Django版本(我相信1.3及更高版本)


3
重要说明:应将其添加到XxAdmin类中。我太快读它,并试图将其添加到我的AdminFormModelForm类和不知道为什么他们没有渲染“只读域”。顺便说一句,有没有可能具有真正的“只读字段形式?
Tomasz Gandor

28

我认为这里最简单(也许也是最优雅)的解决方案是利用您可以设置default为可调用对象的事实。因此,要绕过管理员对auto_now的特殊处理,您可以像这样声明字段:

from django.utils import timezone
date_filed = models.DateField(default=timezone.now)

重要的是不要使用timezone.now()默认值,因为默认值不会更新(即,仅在加载代码时设置默认值)。如果您发现自己经常这样做,则可以创建一个自定义字段。但是,我认为这已经很干燥了。


2
默认值大致等于auto_now_add(首次保存对象时的设置值),但它根本不像auto_now(每次保存对象时的设置值)。
Shai Berger 2013年

1
@ShaiBerger,我认为它们在重要的方面有细微的差别。该文档指出了一个微妙之处:“自动设置字段...;它不仅仅是可以覆盖的默认值。” - docs.djangoproject.com/en/dev/ref/models/fields/...
托马斯- BeeDesk

@ Thomas-BeeDesk:同意。因此,“或多或少等效”。
Shai Berger 2014年

1
如果使用迁移,则此解决方案效果不佳。每次运行时,makemigrations它将默认值解释为运行时间makemigrations,因此认为默认值已更改!
nhinkle

8
@nhinkle,您确定未指定default=timezone.now()而不是建议使用的内容:(default=timezine.now无括号)?
乔什(Josh)2015年

18

如果您像这样更改模型类:

class MyModel(models.Model):
    time = models.DateTimeField(auto_now_add=True)
    time.editable = True

然后,该字段将显示在我的管理员更改页面中


1
但是它仅适用于编辑记录。当我创建新记录-传递到日期时,忽略图块值。当我更改此记录时-设置了新值。
Anton Danilchenko

2
有效,但应该是models.DateTimeField而不是models.DatetimeField
matyas

2
失败python manage.py makemigrations:KeyError:u'editable'–
laoyur

12

根据我已经阅读的内容以及到目前为止的Django经验,auto_now_add确实存在问题。我同意詹森主义---覆盖干净的普通保存方法,您知道正在发生什么。现在,要使其干燥,请创建一个称为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 = ...

如果您希望这些字段显示在admin中,则只需删除该editable=False选项


1
timezone.now()您在这里使用哪个?我假设django.utils.timezone.now(),但我并不乐观。另外,为什么使用timezone.now()而不是datetime.datetime.now()
coredumperror14年

1
好点。我添加了导入语句。使用的原因timezone.now()是因为它是时区感知的,而datetime.datetime.now()时区是幼稚的。您可以在此处阅读有关内容:docs.djangoproject.com/en/dev/topics/i18n/timezones
Edward Newell

@EdwardNewell为什么选择在保存中而不是default=timezone.now在字段构造函数中设置creation_date ?
Blackeagle52

嗯..也许我只是没想到,听起来确实更好。
爱德华·纽厄尔2015年

2
好吧,在某些情况下不会更新last_modified:update_fields提供arg并且列表中没有'last_modified'时,我要添加:if 'update_fields' in kwargs and 'last_modifed' not in kwargs['update_fields']: kwargs['update_fields'].append('last_modified')
danius

5

这值得关注吗?

不,Django在保存模型时会自动为您添加它,因此是可以预期的。

附带问题:在我的管理工具中,这两个字段没有显示。那是预期的吗?

由于这些字段是自动添加的,因此不会显示。

正如synack所说的,除此以外,在django邮件列表上已经有辩论将其删除,因为它“设计得不好”并且是“黑客”。

与使用auto_now相比,在我的每个模型上编写自定义的save()要痛苦得多

显然,您不必将其写入每个模型。您可以将其写入一个模型并从中继承其他模型。

但是,因为auto_addauto_now_add在那里,我会用他们,而不是试图写一个方法我自己。


3

今天我在工作中需要类似的东西。默认值为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.datetime。我没有尝试过DateTime,只有尝试过Date


2

您可以将其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 intauto_now_add将导致错误。

下面是Django默认的代码DateTimeField.pre_saveauto_nowauto_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将导致未在字段中设置任何值。


7
我注意到,如果我们将默认值保留为timezone.now(),则在进行迁移时,(此刻的)实际日期和时间会传递到迁移文件中。我认为我们应该避免这种情况,因为每次您调用makemigrations时,该字段将具有不同的值。
卡兰·库玛

2

至于您的管理员显示,请参阅此答案

注意:auto_now并且默认auto_now_add设置为editable=False,这就是为什么这样。


1

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)

1
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将设置某人修改其反馈的时间。


-1

如果您使用的是南方,并且想要默认为将字段添加到数据库的日期,这就是答案:

选择选项2, 然后: datetime.datetime.now()

看起来像这样:

$ ./manage.py schemamigration myapp --auto
 ? The field 'User.created_date' does not have a default specified, yet is NOT NULL.
 ? Since you are adding this field, you MUST specify a default
 ? value to use for existing rows. Would you like to:
 ?  1. Quit now, and add a default to the field in models.py
 ?  2. Specify a one-off value to use for existing columns now
 ? Please select a choice: 2
 ? Please enter Python code for your one-off default value.
 ? The datetime module is available, so you can do e.g. datetime.date.today()
 >>> datetime.datetime.now()
 + Added field created_date on myapp.User

更新为:datetime和django.utils.timezone模块可用,因此您可以执行以下操作:timezone.now()
michel.iamit 2015年

当您的模型缺少关键数据时,这是一个问卷调查表。如果正确设置了模型,则永远不需要看到此提示。
Shayne
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.