장고에서 특정 모델을 가리키는 역방향 외래 키를 빠짐없이 찾아 수정하는 방법을 다룬 글이다. 순방향 참조는 필드 값만 고치면 되지만 역방향은 그 모델을 참조하는 모든 모델을 찾아야 한다. 모델 클래스의 _meta.related_objects가 그 목록을 준다.
핵심 포인트- 순방향 참조 수정은 대상 모델의 외래 키 필드만 update하면 끝난다.
- 역방향 참조 수정은 그 모델을 가리키는 모든 모델을 찾아 고쳐야 한다.
- 참조 목록은 모델 클래스의 _meta.related_objects에서 얻는다.
- 소스코드를 눈으로 훑어 참조를 세는 방식은 다른 장고 앱에서 참조하는 경우를 놓친다.
- related_objects에는 ManyToOneRel 외에 OneToOneRel, ManyToManyRel도 섞여 있어 수정 방식이 서로 다르다.
상세 정리- 예제 모델: ProductCategory에 속한 Product를 User가 Purchase하거나 Sell하는 구조로 두고 설명한다.
- 순방향 사례: 소식지 상품들의 분류가 BOOK으로 잘못 들어가 MAGAZINE으로 바꿔야 할 때, filter 후 update로 category 필드만 고치면 된다.
- 역방향 사례: 담당자 실수로 같은 제품이 두 번 등록돼 product_2를 지우고 product_1로 합치기로 한 상황이다.
- 삭제 전 선행 작업: product_2를 참조하는 모델이 있으므로 그냥 지우면 안 되고 참조하는 쪽을 먼저 product_1로 바꿔야 한다.
- 직접 지정의 위험: Purchase와 Sell만 고치는 코드는 그 둘이 참조의 전부라는 가정에 기대는데, 장고 앱은 다른 앱에서 가져다 쓸 수 있어 같은 앱 소스에 없어도 참조가 존재할 수 있다.
- _meta의 정체: 장고 모델 클래스의 _meta는 Options 클래스의 인스턴스로 모델의 부가 정보를 담고 있고 그중 관계 정보도 들어 있다.
- related_objects 출력: Product._meta.related_objects를 순회하면 shopping 앱의 purchase, sell 외에 statistics 앱의 productstatistics까지 나온다. 다른 앱의 참조가 실제로 드러나는 지점이다.
- 세부 속성 셋: related_model은 참조한 모델, remote_field.name은 참조한 필드 이름, get_accessor_name()은 역참조 필드 이름을 준다.
- 역참조 이름 규칙: ForeignKey 정의 시 related_name에 준 이름이며, 지정하지 않으면 모델 이름 뒤에 _set이 붙는다. sells로 지정하지 않았다면 sell_set이 됐을 것이다.
- 일괄 수정 코드: related_objects를 돌면서 타입이 ManyToOneRel인 것만 남기고, remote_field.name을 키로 하는 딕셔너리를 언패킹해 filter와 update에 넘긴다.
- 타입 필터링 이유: OneToOneRel과 ManyToManyRel은 수정 방식이 ForeignKey와 달라 예제에서는 건너뛰었을 뿐이며, 실제로 존재한다면 각각에 맞는 방법으로 고쳐야 한다.
- 마무리 순서: 역참조를 모두 옮긴 뒤에야 product_2를 delete한다.
- SQL 대안: 데이터베이스 쪽에서 외래 키 목록을 조회하는 방법도 있으며 Dataedo 문서를 참고로 든다.
왜 읽나장고에서 중복 레코드 병합이나 모델 삭제를 하다 참조 무결성에 걸린 적이 있다면 _meta.related_objects로 참조를 빠짐없이 훑는 패턴을 쓸 수 있다.