ChatGPT解决这个技术问题 Extra ChatGPT

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,

这值得关注吗?

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

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

u
user8193706

具有 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)

希望这可以帮助!

根据评论进行编辑:

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

上述起伏与他们的可靠性。这些参数在很大程度上依赖于 Django 知道如何与之交互的每种类型的数据库处理日期/时间戳字段的方式,并且似乎在每个版本之间中断和/或更改。 (我相信这是呼吁将它们完全移除的动力)。它们仅适用于 DateField、DateTimeField 和 TimeField,并且通过使用此技术,您可以在每次保存项目时自动填充任何字段类型。使用 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)这是数据库实际上擅长的事情,以及参照完整性等。相信数据库会正确运行是一个很好的默认设置,因为比您或我更聪明的人已经设计了数据库以这种方式工作。
P
Paolo

但我想指出,accepted answer 中表达的观点有些过时。根据最近的讨论(django 错误 #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?
D
DataGreed

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

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

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


需要注意的重要一点:这应该添加到 XxAdmin 类中。我读得太快了,试图将它添加到我的 AdminFormModelForm 类中,但不知道他们为什么不呈现“只读字段”。顺便说一句,是否有可能在表单中有真正的“只读字段”?
d
davnicwil

我认为这里最简单(也许也是最优雅)的解决方案是利用您可以将 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 服务以保持正确的日期
O
Oyster773

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

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'
E
Edward Newell

根据我迄今为止阅读的内容和我对 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')
V
Viraj Wadate
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 将设置有人修改他的反馈的时间。
l
lprsd

这值得关注吗?

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

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

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

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

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

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

但是,由于存在 auto_addauto_now_add,我会使用它们而不是尝试自己编写方法。


D
Daniel Holmes

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

注意:auto_nowauto_now_add 默认设置为 editable=False,这就是适用的原因。


这是一个优雅的解决方案!!!
D
Daniel Holmes

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


P
Peter Mortensen

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)

P
Peter Mortensen

您可以使用 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_save 的代码,其中包含 auto_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(无括号)在创建/修改时调用该函数,而不是在迁移时。
J
Jeff Hoye

如果您使用的是 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()
这是您的模型缺少关键数据时的问卷。如果您正确设置模型,则永远不需要看到此提示。