우와 블로그에 글좀 자주 써야겠다.
최근 업무를 보면서 정말 이상한 버그를 하나 발견했다.
태그를 생성하고 붙이는 기능에서 난 오류인데
분명 API 테스트할 때는 아무런 문제가 없다.
프론트에서도 json값 잘 담아서 보내주는데
이상하게 서버에 올리고 일정 시간만 지나면 500 에러가 뜨는 것이다.
서버를 재시작하면 갑자기 같은 환경에서 잘 작동한다.
그렇게 2주간 평화롭게 버그 없이 가다가 또 어느날 확인해보면 500에러가 뜬다.
sentry 들어가서 확인해보니 프론트에서 잘 보내주고 있는 특정 필드 값이 없다고 IntegrityError
를 띄워주고 있었다.
null value in column "project_id" violates not-null constraint
랜덤하게 발생하니 도저히 영문을 알 수 없었다.
여기서 문제 해결의 큰 실마리가 된 부분은
-> 이 경우 대체로 시작할 때 할당해주는 값이 실행 중에 꼬이는 게 문제다. 원인은 달랐지만 예전에 viewset의 get_queryset() 관련으로 비슷한 오류를 마주한 적이 있었는데 이것도 나중에 포스팅해야겠다.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
setattr(self.Meta, 'read_only_fields', ['project', 'category'])
원인은 아니나다를까 Serializer의 __init__
안의 코드 때문이었다.
한번 정해지면 수정 불가능하게 만들기 위해 instance가 있을 경우 해당 필드들(project, category)은 read_only_fields로 만드는 코드가 작성되어있었는데
문제는 우리 툴의 태그 관리 페이지는
태그들을 보여주는(GET) 페이지에 태그 추가 버튼이 있었다.
당연히 태그를 보여주는 API 호출 시에는 instance가 존재하니 project와 category는 그 시점에서는 read_only_fields가 되었다가
이후 태그 추가(POST)를 하려고 하니 read_only_fields로 변한 상태에서 아무리 project와 category를 보내줘도 받아먹질 못하고 있었던 거였다.
해결방법은 매우 간단하다.
setattr(self.Meta, 'read_only_fields', ['project', 'category'] if self.instance else [])
그냥 else 추가해서 아닐 때는 read_only_fileds에서 없애주면 된다.
이런 식으로 처음 생성한 값을 drf에서 수정 불가능하게 만들어야 하는 필드들이 여럿 있다.
사실 이런 필드들은 constrainst나 trigger를 이용해서 수정 불가능한 상태로 제한해야 할 필요가 있다.
작업하자,,