이번 포스팅에서는 DateField의 auto_now_add 옵션을 사용하다가 이슈를 겪었던 경험과, 잘 모르고 있던 내용에 대해서 정리해보겠습니다.
auto_now_add 옵션을 True로 설정하면 얻게되는 효과는 너무 간단합니다. 현재 시간을 기준으로 값이 설정되도록 하는 것입니다. 따라서 보통은 created_at 처럼 객체가 생성된 시각을 기록하는 필드에 사용합니다.
너무 단순하게 생각했던 나머지 세부적인 사항에 대해서는 잘 알지 못했습니다. 코드를 통해 자세히 알아보겠습니다.
문제가 생겼던 코드
Event 모델이 존재하고, 속성으로 이벤트가 시작하는 시간과 종료되는 시간이 있다고 가정하겠습니다.
class Event(models.Model):
name = models.CharField(max_length=256)
started_at = models.DateTimeField(auto_now_add=True)
ended_at = models.DateTimeField()
...
from django.utils import timezone
from datetime import timedelta
now = timezone.now()
create_params = {
'name': 'leffe event',
'started_at': now + timedelta(days=7), # 이 값은 무시됩니다.
'ended_at': now + timedelta(days=14),
}
event = Event.objects.get_or_create(**create_params)
# 값이 무시되어 다시 저장을 시도함.
event.started_at = create_params['started_at']
event.save()
제가 원했던 코드는 Event 생성하는 시점을 지정하고 싶었습니다. 따라서 객체를 생성할 때 값을 지정 해주었습니다.
그렇게 create_params와 함께 객체 생성을 시도하였는데 이 곳에서 문제가 발생했습니다.
바로, 제가 지정한 시간은 무시되고 현재 시각의 정보로 create_at의 값이 저장이 되는 것이었습니다. 처음에는 단지 "값을 할당했는데 왜 자꾸 현재 시간이 들어가지..?" 라는 점을 보고 이상하게 생각했습니다. 그래서 우선은 객체를 생성한 후 create_at 의 값을 다시 변경 -> save() 하고나니 정상적으로 저장되었습니다.
긴급하게 위 처럼 코드를 작성했지만, 무언가 이상함을 느끼고 Document를 찾아보게 되었습니다.
https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.DateField.auto_now_add
auto_now_add 문서 내용
DateField.auto_now_add
Automatically set the field to now when the object is first created. Useful for creation of timestamps. Note that the current date is always used; it’s not just a default value that you can override. So even if you set a value for this field when creating the object, it will be ignored.
If you want to be able to modify this field, set the following instead of auto_now_add=True
For DateField: default=date.today - from datetime.date.today()
For DateTimeField: default=timezone.now - from django.utils.timezone.now()
아뿔싸, 문서에서도 명확히 설명하고 있습니다. auto_now_add 옵션을 활성화 하면 객체 생성시에 지정한 값은 무시가 된다.
첨언으로 필드의 수정을 원하면 default 옵션을 통해 함수를 할당하면 된다고 제안하고 있습니다.
동작하는 원리
조금 더 궁금해서 내부적으로 어떻게 동작 하는지에 대해서도 찾아보게 되었습니다.
# in DateTime class, django.db.models.fields.__init__.py
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.date.today()
setattr(model_instance, self.attname, value)
return value
else:
return super().pre_save(model_instance, add)
코드를 보면 pre_save 라는 시그널 함수를 통해 옵션이 True일 경우 필드에 값을 지정해주는 것을 확인할 수 있었습니다.
이렇기 때문에 값을 할당하여 생성한다 하더라도 무시가 되고, 최종적으로 저장되기 전의 시간으로 덮어 씌워지게 되는 것입니다.
잘 알고 사용하기
auto_now_add는 사실 너무 간단한 옵션이었기에 따로 문서를 읽어본 적이 없었습니다. 하지만 이번 트러블 슈팅을 통해서 내부적으로는 pre_save 시그널을 통해 값을 설정 해준다는 것과, 지정한 값은 무시가 되는 점 또한 알게 되었습니다.
간단하게 객체 생성 후 -> 값을 재 지정해주는 것으로 해결을 하고 있었지만 찜찜한 마음이었기에 조금 더 파고들게 되었습니다. 동작하는 원리를 알고난 후에 auto_now_add 옵션 대신 defaults 옵션을 통해 now 함수를 전달해 줌으로써 깔끔하게 해결할 수 있었습니다.
가끔은 너무 쉽다고 생각한 것들로부터 문제가 발생하기도 합니다. 그럴 때에는 본인이 해당 기능의 원리를 정확하게 이해하고 있는지, 잘못 알고 있는 것은 아닌지를 체크해볼 필요가 있다고 생각합니다.
읽어주셔서 감사합니다 :)
'SW개발 > Django' 카테고리의 다른 글
[Django]SetUpTestData의 격리 지원 (feat. Django > 3.2) (0) | 2022.08.02 |
---|---|
[Django]get_or_create() 란? (0) | 2022.08.01 |
[Django]Side effect 를 방지하는 테스트 코드 작성법 (0) | 2022.03.17 |
[Django]GenericForeignKey의 문제점 (feat. 안티패턴의 지름길) (0) | 2021.11.23 |
[Django]1.11에서 3.2로 버전 업그레이드 방법 (마이그레이션) (0) | 2021.11.23 |