【问题标题】:Temporarily disable auto_now / auto_now_add暂时禁用 auto_now / auto_now_add
【发布时间】:2011-11-21 22:07:00
【问题描述】:

我有一个这样的模型:

class FooBar(models.Model):
    createtime = models.DateTimeField(auto_now_add=True)
    lastupdatetime = models.DateTimeField(auto_now=True)

我想覆盖某些模型实例的两个日期字段(在迁移数据时使用)。当前的解决方案如下所示:

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = False
    elif field.name == "createtime":
        field.auto_now_add = False

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = True
    elif field.name == "createtime":
        field.auto_now_add = True

有没有更好的解决方案?

【问题讨论】:

  • new_entry.createtime.auto_now = False ?
  • +1 - 这对测试来说非常好
  • @akonsu Nope: 'datetime.datetime' 对象没有属性 'auto_now'
  • 值得指出的是,不少核心开发者are in favor of deprecating auto_now(_add)
  • new_entry._meta.get_field('date_update') 更直接

标签: django datetime


【解决方案1】:

来自 django docs

DateField.auto_now_add

在首次创建对象时自动将字段设置为现在。用于创建时间戳。请注意,始终使用当前日期; 它不仅仅是一个可以覆盖的默认值。因此,即使您在创建对象时为此字段设置了值,它也会被忽略。 如果您希望能够修改此字段,请设置以下内容而不是 auto_now_add=True

对于 DateFielddefault=date.today - 来自 datetime.date.today()

对于 DateTimeField:default=timezone.now - 来自 django.utils.timezone.now()

【讨论】:

    【解决方案2】:

    来自https://stackoverflow.com/a/35943149/1731460 的更简洁的上下文管理器版本

    from contextlib import contextmanager
    
    @contextmanager
    def suppress_auto_now(model, field_names):
        """
        idea taken here https://stackoverflow.com/a/35943149/1731460
        """
        fields_state = {}
        for field_name in field_names:
            field = model._meta.get_field(field_name)
            fields_state[field] = {'auto_now': field.auto_now, 'auto_now_add': field.auto_now_add}
    
        for field in fields_state:
            field.auto_now = False
            field.auto_now_add = False
        try:
            yield
        finally:
            for field, state in fields_state.items():
                field.auto_now = state['auto_now']
                field.auto_now_add = state['auto_now_add']
    

    您甚至可以在工厂(工厂男孩)中使用它

            with suppress_autotime(Click, ['created']):
                ClickFactory.bulk_create(post=obj.post, link=obj.link, created__iter=created)
    

    【讨论】:

      【解决方案3】:

      您还可以使用save()update_fields 参数并传递您的auto_now 字段。这是一个例子:

      # Date you want to force
      new_created_date = date(year=2019, month=1, day=1)
      # The `created` field is `auto_now` in your model
      instance.created = new_created_date
      instance.save(update_fields=['created'])
      

      以下是 Django 文档中的解释:https://docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save

      【讨论】:

      • 我希望我能投票得更高!这确实是我想要的(在不触及“自动”字段的情况下更改模型的某些部分),但不幸的是没有回答给出的问题(即使用auto_nowauto_now_add 将显式值保存到字段中) .
      • 最佳答案,我希望我能给它10票,非常简单优雅
      • 必须先创建对象。否则会出现此错误无法在没有主键的情况下强制在 save() 中进行更新
      【解决方案4】:

      您无需特殊代码即可覆盖auto_now_add

      我在尝试创建具有特定日期的对象时遇到了这个问题:

      Post.objects.create(publication_date=date, ...)
      

      publication_date = models.DateField(auto_now_add=True).

      这就是我所做的:

      post = Post.objects.create(...)
      post.publication_date = date
      post.save()
      

      这已成功覆盖auto_now_add

      作为更长期的解决方案,覆盖save 方法是要走的路:https://code.djangoproject.com/ticket/16583

      【讨论】:

        【解决方案5】:

        我需要适用于update_or_create 的解决方案,我是根据@andreaspelme 代码找到这个解决方案的。

        唯一的变化是您可以通过将修改字段设置为 skip 来设置跳过,而不仅仅是通过实际将 kwarg skip_modified_update 传递给 save() 方法。

        只需yourmodelobject.modified='skip' 即可跳过更新!

        from django.db import models
        from django.utils import timezone
        
        
        class TimeTrackableAbstractModel(models.Model):
            created = models.DateTimeField(default=timezone.now, db_index=True)
            modified = models.DateTimeField(default=timezone.now, db_index=True)
        
            class Meta:
                abstract = True
        
            def save(self, *args, **kwargs):
                skip_modified_update = kwargs.pop('skip_modified_update', False)
                if skip_modified_update or self.modified == 'skip':
                    self.modified = models.F('modified')
                else:
                    self.modified = timezone.now()
                super(TimeTrackableAbstractModel, self).save(*args, **kwargs)
        

        【讨论】:

          【解决方案6】:

          Django - Models.DateTimeField - Changing dynamically auto_now_add value的副本

          嗯,我花了今天下午才发现,第一个问题是如何获取模型对象以及在代码中的位置。我在 serializer.py 的 restframework 中,例如在序列化器的__init__ 中它还没有模型。现在在 to_internal_value 中,您可以获取模型类,在获取字段并修改字段属性后,如下例所示:

          class ProblemSerializer(serializers.ModelSerializer):
          
              def to_internal_value(self, data): 
                  ModelClass = self.Meta.model
                  dfil = ModelClass._meta.get_field('date_update')
                  dfil.auto_now = False
                  dfil.editable = True
          

          【讨论】:

            【解决方案7】:

            为了可重用性,我采用了上下文管理器。

            @contextlib.contextmanager
            def suppress_autotime(model, fields):
                _original_values = {}
                for field in model._meta.local_fields:
                    if field.name in fields:
                        _original_values[field.name] = {
                            'auto_now': field.auto_now,
                            'auto_now_add': field.auto_now_add,
                        }
                        field.auto_now = False
                        field.auto_now_add = False
                try:
                    yield
                finally:
                    for field in model._meta.local_fields:
                        if field.name in fields:
                            field.auto_now = _original_values[field.name]['auto_now']
                            field.auto_now_add = _original_values[field.name]['auto_now_add']
            

            这样使用:

            with suppress_autotime(my_object, ['updated']):
                my_object.some_field = some_value
                my_object.save()
            

            轰隆隆。

            【讨论】:

              【解决方案8】:

              对于那些在编写测试时看到这一点的人,有一个名为 freezegun 的 python 库可以让你伪造时间 - 所以当 auto_now_add 代码运行时,它会得到你真正想要的时间。所以:

              from datetime import datetime, timedelta
              from freezegun import freeze_time
              
              with freeze_time('2016-10-10'):
                  new_entry = FooBar.objects.create(...)
              with freeze_time('2016-10-17'):
                  # use new_entry as you wish, as though it was created 7 days ago
              

              它也可以用作装饰器 - 有关基本文档,请参阅上面的链接。

              【讨论】:

                【解决方案9】:

                我使用了提问者的建议,并创建了一些功能。这是用例:

                turn_off_auto_now(FooBar, "lastupdatetime")
                turn_off_auto_now_add(FooBar, "createtime")
                
                new_entry.createtime = date
                new_entry.lastupdatetime = date
                new_entry.save()
                

                下面是实现:

                def turn_off_auto_now(ModelClass, field_name):
                    def auto_now_off(field):
                        field.auto_now = False
                    do_to_model(ModelClass, field_name, auto_now_off)
                
                def turn_off_auto_now_add(ModelClass, field_name):
                    def auto_now_add_off(field):
                        field.auto_now_add = False
                    do_to_model(ModelClass, field_name, auto_now_add_off)
                
                def do_to_model(ModelClass, field_name, func):
                    field = ModelClass._meta.get_field_by_name(field_name)[0]
                    func(field)
                

                可以创建类似的功能来重新打开它们。

                【讨论】:

                • 在大多数情况下,您可以只做Clazz._meta.get_field_by_name(field_name)[0],而不是迭代。
                • 谢谢@naktinis。改变了。
                • n.b. (1.10) Do_to_model 中的 ModelClass._meta.get_field_by_name(field_name)[0] 似乎对我不起作用 - 更改为:ModelClass._meta.get_field(field_name)
                【解决方案10】:

                我迟到了,但与其他几个答案类似,这是我在数据库迁移期间使用的解决方案。与其他答案的不同之处在于,这会禁用模型的 all auto_now 字段,假设确实没有理由拥有多个此类字段。

                def disable_auto_now_fields(*models):
                    """Turns off the auto_now and auto_now_add attributes on a Model's fields,
                    so that an instance of the Model can be saved with a custom value.
                    """
                    for model in models:
                        for field in model._meta.local_fields:
                            if hasattr(field, 'auto_now'):
                                field.auto_now = False
                            if hasattr(field, 'auto_now_add'):
                                field.auto_now_add = False
                

                然后要使用它,你可以简单地这样做:

                disable_auto_now_fields(Document, Event, ...)
                

                它将遍历并删除您传入的所有模型类的所有 auto_nowauto_now_add 字段。

                【讨论】:

                  【解决方案11】:

                  我需要在迁移期间为 DateTime 字段禁用 auto_now,并且能够做到这一点。

                  events = Events.objects.all()
                  for event in events:
                      for field in event._meta.fields:
                          if field.name == 'created_date':
                              field.auto_now = False
                      event.save()
                  

                  【讨论】:

                    【解决方案12】:

                    我最近在测试我的应用程序时遇到了这种情况。我需要“强制”一个过期的时间戳。就我而言,我通过使用查询集更新来做到这一点。像这样:

                    # my model
                    class FooBar(models.Model):
                        title = models.CharField(max_length=255)
                        updated_at = models.DateTimeField(auto_now=True, auto_now_add=True)
                    
                    
                    
                    # my tests
                    foo = FooBar.objects.get(pk=1)
                    
                    # force a timestamp
                    lastweek = datetime.datetime.now() - datetime.timedelta(days=7)
                    FooBar.objects.filter(pk=foo.pk).update(updated_at=lastweek)
                    
                    # do the testing.
                    

                    【讨论】:

                    • 感谢您的回答。以下是 update() 的文档:docs.djangoproject.com/en/dev/ref/models/querysets/…
                    • 实际上,如果您不介意访问数据库,此方法效果很好。我最终也将其用于测试。
                    • 来自 Django 文档:docs.djangoproject.com/en/1.9/topics/db/queries/… 请注意,update() 方法直接转换为 SQL 语句。这是直接更新的批量操作。它不会在您的模型上运行任何 save() 方法,也不会发出 pre_save 或 post_save 信号(这是调用 save() 的结果),或者尊重 auto_now 字段选项
                    • @NoamG 我认为这种update() 行为正是我们所需要的。
                    【解决方案13】:

                    您无法以其他方式真正禁用 auto_now/auto_now_add。如果您需要灵活地更改这些值,auto_now/auto_now_add 不是最佳选择。在保存对象之前使用default 和/或覆盖save() 方法进行操作通常更灵活。

                    使用default 和覆盖的save() 方法,解决您的问题的一种方法是这样定义您的模型:

                    class FooBar(models.Model):
                        createtime = models.DateTimeField(default=datetime.datetime.now)
                        lastupdatetime = models.DateTimeField()
                    
                        def save(self, *args, **kwargs):
                            if not kwargs.pop('skip_lastupdatetime', False):
                                self.lastupdatetime = datetime.datetime.now()
                    
                            super(FooBar, self).save(*args, **kwargs)
                    

                    在您想要跳过自动 lastupdatetime 更改的代码中,只需使用

                    new_entry.save(skip_lastupdatetime=True)
                    

                    如果您的对象保存在管理界面或其他地方,将在没有 skip_lastupdatetime 参数的情况下调用 save(),它的行为与之前使用 auto_now 时一样。

                    【讨论】:

                    • TL;DR 不要使用auto_now_add,而是使用default
                    • 对这个例子的一个警告是datetime.datetime.now 返回一个简单的日期时间。要使用时区感知日期时间,请使用 from django.utils import timezonemodels.DateTimeField(default=timezone.now) 请参阅 docs.djangoproject.com/en/1.9/topics/i18n/timezones/…
                    • 所以,要明确一点:如果您只希望能够修改createtime 字段,则不需要覆盖save()。将auto_now_add=True 替换为等效的default=timezone.now, editable=False, blank=True(根据docs)就足够了。后两个选项可确保管理员中的类似行为。
                    猜你喜欢
                    • 2021-04-20
                    • 2010-12-16
                    • 1970-01-01
                    • 2018-12-25
                    • 2013-02-14
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-12-25
                    • 2014-02-28
                    相关资源
                    最近更新 更多