일시적으로 auto_now / auto_now_add 비활성화


104

다음과 같은 모델이 있습니다.

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?
akonsu

3
+
1-

@akonsu Nope : 'datetime.datetime'개체에 'auto_now'속성이 없습니다.
mlissner

2
그것은의 가치가 몇 가지 핵심 개발자들보다는 더 많은 것을 지적 비하의 찬성auto_now(_add)
mlissner

new_entry._meta.get_field ( 'date_update')가 더 직접적입니다
Sérgio

답변:


99

최근에 애플리케이션을 테스트하는 동안 이러한 상황에 직면했습니다. 만료 된 타임 스탬프를 "강제"해야했습니다. 제 경우에는 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.

이 답변에 감사드립니다. 다음은 갱신 ()에 대한 문서입니다 docs.djangoproject.com/en/dev/ref/models/querysets/...
guettli

1
사실,이 방법은 데이터베이스를 쳐도 괜찮다면 잘 작동합니다. 나는 이것을 테스트에도 사용했습니다.
imjustmatthew

3
Django 문서에서 : docs.djangoproject.com/en/1.9/topics/db/queries/… update () 메서드는 SQL 문으로 직접 변환됩니다. 직접 업데이트를위한 대량 작업입니다. 모델에서 save () 메소드를 실행하지 않거나 pre_save 또는 post_save 신호 (save ()를 호출 한 결과)를 방출하거나 auto_now 필드 옵션을
준수하지 않습니다

@NoamG 나는 이것이 update()우리에게 필요한 행동이 정확히 필요한 드문 경우라고 생각 합니다.
David D.

54

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.


14
TL; DR 마십시오는 사용하지 않을 auto_now_add사용을 default대신.
Thane Brimhall 2015

9
이 예제에 대한 한 가지주의 사항 datetime.datetime.now은 순진한 날짜 시간 을 반환한다는 것입니다. 시간대 인식 날짜, 사용, 사용 from django.utils import timezonemodels.DateTimeField(default=timezone.now)참조 docs.djangoproject.com/en/1.9/topics/i18n/timezones/...
BenjaminGolder

따라서 명확하게 말하면 createtime필드 를 수정할 수만 있으면 을 재정의 할 필요가 없습니다 save(). auto_now_add=True동등한 것으로 대체 하는 것으로 충분합니다 default=timezone.now, editable=False, blank=True( docs 에 따라 ). 후자의 두 옵션은 관리자에서 유사한 동작을 보장합니다.
djvg

28

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

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)

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


9
대부분의 경우 반복 대신 할 수 Clazz._meta.get_field_by_name(field_name)[0]있습니다.
naktinis

감사합니다 @naktinis. 변경되었습니다.
Frank Henard는

4
NB는 (1.10) ModelClass._meta.get_field_by_name (FIELD_NAME) [0] do_to_model에서 나를 위해 작동하는 것 같지 않았다 - 변경 : ModelClass._meta.get_field (FIELD_NAME)
조지나 S

27

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


1
더 높이 투표 할 수 있으면 좋겠어요! 이것은 실제로 내가 원했던 것입니다 ( 'auto'필드를 건드리지 않고 모델의 일부를 변경하려는 것입니다). 그러나 불행히도 주어진 질문에 대답하지 않습니다 ( auto_now그리고 사용하는 필드에 명시적인 값을 저장하는 것입니다 auto_now_add).
Tim Tisdall

1
가장 좋은 대답, 나는 그것을 매우 간단하고 우아한 10 표, 제공 할 수 있습니다 소원
Bedros

18

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

@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 이라는 파이썬 라이브러리가있어 시간을 가짜로 만들 수 있습니다. 따라서 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

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


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


2

마이그레이션 중에 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()

1

나는 파티에 늦었지만 다른 여러 답변과 유사하게 이것은 데이터베이스 마이그레이션 중에 사용한 솔루션입니다. 다른 답변과의 차이점은 하나 이상의 필드를 가질 이유가 없다는 가정하에 모델의 모든 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필드를 모두 통과하고 핵무기를 할 것 입니다.


1

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)

0

Django 사본 -Models.DateTimeField-동적으로 auto_now_add 값 변경

글쎄, 오늘 오후를 보냈고 첫 번째 문제는 모델 객체를 가져 오는 방법과 코드의 위치입니다. 나는 serializer.py의 restframework에 있습니다. 예를 들어 __init__serializer에서는 Model을 아직 가질 수 없었습니다. 이제 to_internal_value에서 Field를 가져온 후 다음 예제와 같이 필드 속성을 수정 한 후 모델 클래스를 가져올 수 있습니다.

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

0

와 함께 작동하는 솔루션이 필요했습니다 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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.