Временно отключить auto_now/auto_now_add
У меня есть такая модель:
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
Есть ли лучшее решение?
Ответы
Ответ 1
Недавно я столкнулся с этой ситуацией при тестировании своего приложения. Мне нужно было "заставить" истекшую временную метку. В моем случае я сделал трюк, используя обновление набора запросов. Вот так:
# 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.
Ответ 2
Вы не можете отключить 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)
Если ваш объект сохраняется в интерфейсе администратора или в других местах, save() будет вызываться без аргумента skip_lastupdatetime, и он будет вести себя так же, как раньше, с помощью auto_now
.
Ответ 3
Я использовал предложение, высказанное апеллятором, и создал некоторые функции. Вот пример использования:
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)
Аналогичные функции могут быть созданы, чтобы снова включить их.
Ответ 4
Вы также можете использовать параметр update_fields
в save()
и передать свои поля 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
Ответ 5
Я перешел в контекстный менеджер для повторного использования.
@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()
Boom.
Ответ 6
Для тех, кто смотрит на это, когда они пишут тесты, есть библиотека Python freezegun, которая позволяет вам подделывать время - поэтому, когда выполняется код 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
Он также может быть использован в качестве декоратора - см. ссылку выше для основных документов.
Ответ 7
Вы можете переопределить 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
Ответ 8
Мне нужно было отключить auto_now для поля DateTime во время миграции и было в состоянии сделать это.
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()
Ответ 9
Я опаздываю на вечеринку, но, подобно нескольким другим ответам, это решение, которое я использовал во время миграции базы данных. Отличие от других ответов заключается в том, что это отключает поля all auto_now для модели в предположении, что на самом деле нет причин иметь более одного такого поля.
def disable_auto_now_fields(*models):
"""Turns off the auto_now and auto_now_add attributes on a Model 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_now
и auto_now_add
для всех классов модели, в которые вы проходите.
Ответ 10
копия Django - Models.DateTimeField - изменение динамического значения auto_now_add
Хорошо, я провел этот день, узнав, и первая проблема заключается в том, как получить модельный объект и где в коде. Я в режиме restricework в serializer.py, например, в __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
Ответ 11
Мне нужно решение, которое будет работать с 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)