Временно отключите auto_now / auto_now_add - PullRequest
85 голосов
/ 21 сентября 2011

У меня есть такая модель:

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

Есть ли лучшее решение?

Ответы [ 11 ]

88 голосов
/ 03 июля 2012

Я недавно сталкивался с такой ситуацией при тестировании своего приложения. Мне нужно было «заставить» просроченную временную метку В моем случае я добился цели с помощью обновления набора запросов. Как это:

# 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.
51 голосов
/ 21 сентября 2011

Вы не можете действительно отключить 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)

В вашем коде, где вы хотите пропустить автоматическое изменение времени последнего обновления, просто используйте

new_entry.save(skip_lastupdatetime=True)

Если ваш объектсохраненные в интерфейсе администратора или в других местах, save () будет вызываться без аргумента skip_lastupdatetime, и он будет вести себя так же, как и раньше с auto_now.

22 голосов
/ 14 октября 2011

Я воспользовался предложением аскера и создал несколько функций. Вот пример использования:

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)

Аналогичные функции могут быть созданы для их повторного включения.

16 голосов
/ 11 марта 2016

Я использовал контекстный менеджер для повторного использования.

@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.

15 голосов
/ 17 января 2017

Вы также можете использовать параметр update_fields для save(), если знаете, какими полями вы хотите ограничить его, и исключить поля auto_now / auto_now_add:

https://docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save

7 голосов
/ 04 ноября 2016

Для тех, кто смотрит на это, когда они пишут тесты, есть библиотека 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

Он также может быть использован в качестве декоратора - см. Ссылку выше для основных документов.

2 голосов
/ 13 мая 2015

Мне нужно было отключить 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()
1 голос
/ 05 января 2019

Вы можете переопределить 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

1 голос
/ 11 сентября 2015

Я опаздываю на вечеринку, но, как и в некоторых других ответах, это решение я использовал при переносе базы данных. Отличие от других ответов заключается в том, что это отключает все поля 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_now и auto_now_add для всех классов модели, которые вы передаете.

0 голосов
/ 08 августа 2018

Мне нужно решение, которое будет работать с 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...