【问题标题】:Django auto_now and auto_now_addDjango auto_now 和 auto_now_add
【发布时间】:2010-12-16 18:13:15
【问题描述】:

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

这值得关注吗?

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

【问题讨论】:

  • 您是否使用自定义主键而不是默认的自动增量 int?我发现使用自定义主键会导致这个问题。无论如何,我想你现在已经解决了。但是漏洞仍然存在。只是我的 0.02 美元
  • 再提醒一件事。 update() 方法不会调用save(),这意味着它不能自动更新modified 字段

标签: python django datetime django-models django-admin


【解决方案1】:

具有auto_now 属性集的任何字段也将继承editable=False,因此不会显示在管理面板中。过去曾有人谈论过让 auto_nowauto_now_add 参数消失,尽管它们仍然存在,但我觉得你最好只使用 custom save() method

因此,为了使其正常工作,我建议不要使用 auto_nowauto_now_add 而是定义自己的 save() 方法以确保仅在未设置 id 时更新 created (例如首次创建项目时),并在每次保存项目时更新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)

希望这会有所帮助!

编辑以响应 cmets:

我坚持重载save() 与依赖这些字段参数的原因有两个:

  1. 上述起伏与可靠性。这些论点在很大程度上依赖于 Django 知道如何与之交互的每种类型的数据库处理日期/时间戳字段的方式,并且似乎在每个版本之间中断和/或更改。 (我相信这是呼吁将它们完全移除的动力)。
  2. 它们仅适用于 DateField、DateTimeField 和 TimeField,并且通过使用此技术,您可以在每次保存项目时自动填充任何字段类型。
  3. 使用 django.utils.timezone.now()datetime.datetime.now(),因为它会根据 settings.USE_TZ 返回 TZ 感知或幼稚 datetime.datetime 对象。

为了解决 OP 看到错误的原因,我不知道确切的原因,但看起来 created 甚至根本没有被填充,尽管有 auto_now_add=True。对我来说,它是一个错误,并强调了上面我的小列表中的第 1 项:auto_nowauto_now_add 充其量是片状的。

【讨论】:

  • 但是作者问题的根源是什么? auto_now_add 是否有时无法正常工作?
  • 我和你在一起,德米特里。我很好奇为什么这两个字段会抛出错误。我更好奇为什么您认为编写自己的自定义 save() 方法更好?
  • 在我的每个模型上编写自定义 save() 比使用 auto_now 更痛苦(因为我喜欢在我的所有模型上都有这些字段)。为什么这些参数不起作用?
  • @TM,但这需要直接摆弄你的数据库,而 Django 只针对 models.py 文件来定义架构
  • 我强烈反对。 1) editable=False 是正确的,你不应该编辑这个字段,你的数据库需要是准确的。 2) 有各种边缘情况可能不会调用 save(),特别是在自定义 SQL 更新或正在使用的任何情况下。 3)这是数据库实际上擅长的事情,以及参照完整性等。相信数据库会正确处理它是一个很好的默认设置,因为比您或我更聪明的人已经将数据库设计为以这种方式工作。
【解决方案2】:

但我想指出,accepted answer 中表达的观点有些过时了。根据最近的讨论(django bugs #7634#12785),auto_nowauto_now_add 不会去任何地方,即使你去 original discussion,你会发现反对 RY 的有力论据(如在 DRY 中)在自定义保存方法中。

已经提供了更好的解决方案(自定义字段类型),但没有获得足够的动力将其纳入 django。您可以用三行编写自己的代码(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 = AutoDateTimeField(default=timezone.now)

【讨论】:

  • 三行自定义字段在这里:link
  • 我不认为自定义字段是真正必要的,因为您可以将默认设置为可调用(即 timezone.now)。请参阅下面的答案。
  • 这与 auto_add 在 Django 中所做的相同,并且自 2010 年以来一直是:github.com/django/django/blob/1.8.4/django/db/models/fields/…。除非我在 pre_save 中需要额外的钩子,否则我会坚持使用 auto_add。
  • 在 Django 1.9 中对我不起作用,所以这个解决方案并不适用于任何地方,因为它从未适用于 auto_now*。唯一适用于每个用例的解决方案(即使存在“update_fields”参数问题)是覆盖保存
  • 为什么设置默认为timezone.now,而pre_save信号却使用datetime.datetime.now?
【解决方案3】:

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

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

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

【讨论】:

  • 重要提示:这应该添加到XxAdmin 类中。我读得太快了,试图将它添加到我的AdminFormModelForm 类中,但不知道他们为什么不呈现“只读字段”。顺便说一句,是否有可能在表单中有真正的“只读字段?
【解决方案4】:

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

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

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

【讨论】:

  • 默认值或多或少等价于 auto_now_add(对象第一次保存时设置值),但它一点也不像 auto_now(每次保存对象时设置值)。跨度>
  • @ShaiBerger,我认为它们在一个重要方面是微妙的不同。文档说明了微妙之处:“自动设置字段……;它不仅仅是一个可以覆盖的默认值。” -- docs.djangoproject.com/en/dev/ref/models/fields/…
  • 如果您使用迁移,此解决方案效果不佳。每次运行makemigrations 时,它都会将默认值解释为运行makemigrations 的时间,因此认为默认值已更改!
  • @nhinkle,您确定您没有指定default=timezone.now(),而不是推荐的内容:default=timezine.now(无括号)?
  • 不工作。它设置一次默认时间。尽管日期发生变化,但它始终使用相同的时间。您必须每天重新启动 django 服务以保持以下日期正确
【解决方案5】:

如果你像这样改变你的模型类:

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

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

【讨论】:

  • 但它仅适用于编辑记录。当我创建新记录时 - 传递给日期的磁贴值被忽略。当我更改此记录时 - 设置了新值。
  • 有效,但应该是 models.DateTimeField 而不是 models.DatetimeField
  • python manage.py makemigrations 中失败:KeyError: u'editable'
【解决方案6】:

根据我目前阅读的内容和我使用 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 选项

【讨论】:

  • 您在这里使用的是哪个timezone.now()?我假设django.utils.timezone.now(),但我并不积极。另外,为什么使用timezone.now() 而不是datetime.datetime.now()
  • 好点。我添加了导入语句。使用timezone.now() 的原因是因为它是时区感知的,而datetime.datetime.now() 是时区天真的。你可以在这里阅读:docs.djangoproject.com/en/dev/topics/i18n/timezones
  • @EdwardNewell 为什么选择在保存中设置creation_date,而不是在字段构造函数中设置default=timezone.now
  • 嗯..也许我只是没想到,听起来确实更好。
  • 好吧,有一种情况 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')
【解决方案7】:
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 将设置有人修改反馈的时间。

【讨论】:

  • 这很准确,我使用的是 Django 3.2.6 版本,这对我来说非常完美,最简洁优雅。请每个人都应该坚持这个答案并完成它。 auto_now_add 将设置创建实例的时间,而 auto_now 将设置有人修改他的反馈的时间。
【解决方案8】:

这值得关注吗?

不,Django 会在保存模型时自动为您添加它,所以这是意料之中的。

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

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

补充一下,正如 synack 所说,在 django 邮件列表上一直在争论要删除它,因为它“设计得不好”并且是“黑客”

在我的每个模型上编写自定义 save() 比使用 auto_now 更痛苦

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

但是,既然有auto_addauto_now_add,我会使用它们而不是自己尝试编写方法。

【讨论】:

    【解决方案9】:

    至于您的管理员显示,请参阅this answer

    注意:auto_nowauto_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.date 更改为datetime.datetime 或更好的timezone.datetime。我没有用DateTime尝试过,只有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 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 会导致字段中没有设置任何值。

        【讨论】:

        • 我注意到,如果我们保持默认设置为 timezone.now(),当你进行迁移时,实际的日期和时间(这一刻)将传递给迁移文件。我认为我们应该避免这种情况,因为每次调用 makemigrations 这个字段都会有不同的值。
        • 认为您应该使用default=timezone.now(无括号)在创建/修改时调用该函数,而不是在迁移时调用。
        【解决方案13】:

        如果您使用的是 south,并且希望默认为将字段添加到数据库中的日期,则答案如下:

        选择选项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()
        • 这是您的模型缺少关键数据时的问卷。如果你的模型设置正确,你应该永远不需要看到这个提示。
        猜你喜欢
        • 2021-04-20
        • 1970-01-01
        • 2018-12-25
        • 2011-11-21
        • 2013-02-14
        • 1970-01-01
        • 1970-01-01
        • 2018-02-01
        • 2018-06-27
        相关资源
        最近更新 更多