[django] 일시적으로 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

더 나은 해결책이 있습니까?



답변

최근에 애플리케이션을 테스트하는 동안 이러한 상황에 직면했습니다. 만료 된 타임 스탬프를 “강제”해야했습니다. 제 경우에는 queryset 업데이트를 사용하여 트릭을 수행했습니다. 이렇게 :

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


답변

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.


답변

나는 질문자가 제시 한 제안을 사용하여 몇 가지 기능을 만들었습니다. 사용 사례는 다음과 같습니다.

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)

유사한 기능을 만들어 다시 켤 수 있습니다.


답변

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


답변

재사용 성을 위해 컨텍스트 관리자 방식을 사용했습니다.

@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()

팔.


답변

테스트를 작성할 때 이것을 보는 사람들을 위해 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

데코레이터로도 사용할 수 있습니다. 기본 문서는 위의 링크를 참조하세요.


답변

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