Django auto_now和auto_now_add

问题:Django auto_now和auto_now_add

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

这值得关注吗?

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

For Django 1.1.

I have this in my models.py:

class User(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

When updating a row I get:

[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)

The relevant part of my database is:

  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,

Is this cause for concern?

Side question: in my admin tool, those two fields aren’t showing up. Is that expected?


回答 0

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充其量是片状的。

Any field with the auto_now attribute set will also inherit editable=False and therefore will not show up in the admin panel. There has been talk in the past about making the auto_now and auto_now_add arguments go away, and although they still exist, I feel you’re better off just using a custom save() method.

So, to make this work properly, I would recommend not using auto_now or auto_now_add and instead define your own save() method to make sure that created is only updated if id is not set (such as when the item is first created), and have it update modified every time the item is saved.

I have done the exact same thing with other projects I have written using Django, and so your save() would look like this:

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)

Hope this helps!

Edit in response to comments:

The reason why I just stick with overloading save() vs. relying on these field arguments is two-fold:

  1. The aforementioned ups and downs with their reliability. These arguments are heavily reliant on the way each type of database that Django knows how to interact with treats a date/time stamp field, and seems to break and/or change between every release. (Which I believe is the impetus behind the call to have them removed altogether).
  2. The fact that they only work on DateField, DateTimeField, and TimeField, and by using this technique you are able to automatically populate any field type every time an item is saved.
  3. Use django.utils.timezone.now() vs. datetime.datetime.now(), because it will return a TZ-aware or naive datetime.datetime object depending on settings.USE_TZ.

To address why the OP saw the error, I don’t know exactly, but it looks like created isn’t even being populated at all, despite having auto_now_add=True. To me it stands out as a bug, and underscores item #1 in my little list above: auto_now and auto_now_add are flaky at best.


回答 1

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

But I wanted to point out that the opinion expressed in the accepted answer is somewhat outdated. According to more recent discussions (django bugs #7634 and #12785), auto_now and auto_now_add are not going anywhere, and even if you go to the original discussion, you’ll find strong arguments against the RY (as in DRY) in custom save methods.

A better solution has been offered (custom field types), but didn’t gain enough momentum to make it into django. You can write your own in three lines (it’s Jacob Kaplan-Moss’ suggestion).

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)

回答 2

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

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

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

Talking about a side question: if you want to see this fields in admin (though, you won’t be able to edit it), you can add readonly_fields to your admin class.

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

Well, this applies only to latest Django versions (I believe, 1.3 and above)


回答 3

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

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

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

I think the easiest (and maybe most elegant) solution here is to leverage the fact that you can set default to a callable. So, to get around admin’s special handling of auto_now, you can just declare the field like so:

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

It’s important that you don’t use timezone.now() as the default value wouldn’t update (i.e., default gets set only when the code is loaded). If you find yourself doing this a lot, you could create a custom field. However, this is pretty DRY already I think.


回答 4

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

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

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

If you alter your model class like this:

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

Then this field will show up in my admin change page


回答 5

根据我已经阅读的内容以及到目前为止的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选项

Based on what I’ve read and my experience with Django so far, auto_now_add is buggy. I agree with jthanism — override the normal save method it’s clean and you know what’s hapenning. Now, to make it dry, create an abstract model called 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

And then, when you want a model that has this time-stampy behavior, just subclass:

MyNewTimeStampyModel(TimeStamped):
    field1 = ...

If you want the fields to show up in admin, then just remove the editable=False option


回答 6

这值得关注吗?

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

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

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

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

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

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

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

Is this cause for concern?

No, Django automatically adds it for you while saving the models, so, it is expected.

Side question: in my admin tool, those 2 fields aren’t showing up. Is that expected?

Since these fields are auto added, they are not shown.

To add to the above, as synack said, there has been a debate on the django mailing list to remove this, because, it is “not designed well” and is “a hack”

Writing a custom save() on each of my models is much more pain than using the auto_now

Obviously you don’t have to write it to every model. You can write it to one model and inherit others from it.

But, as auto_add and auto_now_add are there, I would use them rather than trying to write a method myself.


回答 7

今天我在工作中需要类似的东西。默认值为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

I needed something similar today at work. Default value to be timezone.now(), but editable both in admin and class views inheriting from FormMixin, so for created in my models.py the following code fulfilled those requirements:

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

For DateTimeField, I guess remove the .date() from the function and change datetime.date to datetime.datetime or better timezone.datetime. I haven’t tried it with DateTime, only with Date.


回答 8

您可以将其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将导致未在字段中设置任何值。

You can use timezone.now() for created and auto_now for modified:

from django.utils import timezone
class User(models.Model):
    created = models.DateTimeField(default=timezone.now())
    modified = models.DateTimeField(auto_now=True)

If you are using a custom primary key instead of the default auto- increment int, auto_now_add will lead to a bug.

Here is the code of Django’s default DateTimeField.pre_save withauto_now and 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)

I am not sure what the parameter add is. I hope it will some thing like:

add = True if getattr(model_instance, 'id') else False

The new record will not have attr id, so getattr(model_instance, 'id') will return False will lead to not setting any value in the field.


回答 9

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

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

As for your Admin display, see this answer.

Note: auto_now and auto_now_add are set to editable=False by default, which is why this applies.


回答 10

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)

auto_now=True didn’t work for me in Django 1.4.1, but the below code saved me. It’s for timezone aware datetime.

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)

回答 11

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

class Feedback(models.Model):
   feedback = models.CharField(max_length=100)
   created = models.DateTimeField(auto_now_add=True)
   updated = models.DateTimeField(auto_now=True)

Here, we have created and updated columns that will have a timestamp when created, and when someone modified feedback.

auto_now_add will set time when an instance is created whereas auto_now will set time when someone modified his feedback.


回答 12

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

选择选项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

Here’s the answer if you’re using south and you want to default to the date you add the field to the database:

Choose option 2 then: datetime.datetime.now()

Looks like this:

$ ./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