- 발행일
처음 만든 트리를 열흘 만에 통째로 다시 짰다 — 템플릿 안 550줄이 어디서 무너졌나
처음 만든 트리를 열흘 만에 통째로 다시 짰다
이 글은 2026년 9월에 당시 커밋 이력을 다시 보며 정리한 것입니다. 날짜는 작업한 날 기준입니다.
2025년 6월, 입사 두 달째였습니다. 맡은 화면은 메타데이터 클래스 트리였어요. 온톨로지의 클래스 계층을 왼쪽에 트리로 그리고, 노드를 고르면 오른쪽에 그 클래스의 속성 표를 보여 주고, 관리자 화면에서는 클래스를 추가·편집·삭제하는 화면입니다.
jsTree는 4월에 기본 사용법을 정리해 둔 상태라 낯설지 않았습니다. 낯선 건 Django 쪽이었어요. 뷰가 컨텍스트를 어떻게 넘기고, 템플릿이 어디서 끝나고 스크립트가 어디서 시작하는지가 아직 몸에 안 붙어 있었습니다.
6월 10일 — 템플릿 안에 전부 넣었다
첫 커밋(c4263200)은 5개 파일에 1,046줄을 더했습니다. 그중 관리자 상세 템플릿 하나가 39줄에서 724줄이 됐어요. 168행부터 719행까지가 <script> 하나입니다.
그 안에 든 것을 세어 보면 이렇습니다. 로딩 표시 run_waitMe, 쿠키에서 CSRF 토큰을 읽는 getCookie, $.ajaxSetup, 클래스 생성·조회·수정·삭제 함수 넷, jsTree 초기화와 컨텍스트 메뉴, 모달 열고 닫기. 화면 하나가 필요로 하는 걸 전부 그 화면 파일에 넣었습니다.
공개 화면에도 같은 트리가 필요했습니다. 그래서 공개 상세 템플릿에도 39줄에서 213줄로, 읽기 전용 복사본을 한 벌 더 넣었어요. run_waitMe와 showMetaClassTable이 두 파일에 따로 있었습니다.
주소는 대부분 {{ '...' | url }} 필터로 만들었는데 한 곳은 아니었습니다.
$.ajax({
url: "/std-data/rest-api/meta-class/" + selected.id + "/",
노드 선택은 이때도 Ajax였습니다.
.on("select_node.jstree", function(e, data) {
if (isEditMode) {
e.preventDefault();
return false;
}
showMetaClassTable(data.node.id, data.node.text, data.node.original.description);
})
다만 트리가 상세 페이지 안에 있었습니다. 주소가 클래스 하나를 가리키는 /meta-class/<id> 꼴이라, 페이지가 뜨면 정규식으로 주소 끝자리를 읽어 그 노드를 골라 줬어요.
function getMetaClassIdFromUrl() {
const matches = window.location.pathname.match(/\/meta-class\/(\d+)$/);
return matches ? matches[1] : null;
}
왜 스크립트를 파일로 빼지 않았는지는 커밋에 남아 있지 않습니다. 짐작만 하면, 그 화면에서만 쓰는 코드라 그 화면 파일에 두는 게 자연스러워 보였던 것 같아요.
6월 19일 — 파일로 빼면서, 클릭마다 페이지를 이동시켰다
아흐레 뒤 커밋(d8131087)은 8개 파일에 +1,360 −1,484입니다. static/js/meta-class-tree.js(614줄)와 concept-class-tree.js(789줄)를 새로 만들고, 관리자 상세 템플릿은 724줄에서 160줄로, 공개 상세는 213줄에서 92줄로 줄었어요. 분류체계 상세 템플릿도 538줄이 바뀌었는데 거의 삭제예요.
템플릿에는 전역 변수 네 개만 남겼습니다.
<script>
window.initialTreeData = `{{ (data.core if data.core else []) | safe }}`;
window.metaClassListUrl = "{{ 'std-data:meta-class-property-list' | url }}";
window.metaListUrl = "{{ 'std-data:meta-class-list' | url }}";
</script>
<script defer type="text/javascript" src="{{ 'js/meta-class-tree.js' | static }}"></script>
파일로 뺐다고 복사본이 사라진 건 아니었습니다. 두 JS 파일에 getCookie·run_waitMe·$.ajaxSetup이 각각 한 벌씩 있었어요. 복사본이 템플릿에서 JS 파일로 자리만 옮긴 겁니다.
그리고 이 커밋에서 이걸 넣었습니다.
// 관리자 페이지에서만 URL 변경
if (isAdmin() && window.location.pathname.includes("/web-admin/")) {
const currentPath = window.location.pathname;
const currentId = currentPath.split("/").pop();
const newUrl = `/web-admin/std-data/meta-class/${data.node.id}`;
if (currentId !== data.node.id) {
window.location.href = newUrl;
}
}
노드를 고르면 Ajax로 표를 그리고, 그다음 페이지를 통째로 이동합니다. 트리를 서버에서 다시 받아 다시 그리고, 방금 그린 표도 다시 그려요. 주소를 클래스에 맞추고 싶었던 건데 방법이 새로고침이었습니다. isAdmin()은 주소 문자열에 web-admin이 들어 있는지로 판단했습니다.
6월 20일 — 하루 만에 이동을 걷어내고 트리를 목록으로 옮겼다
다음날 커밋(702b0502)은 7개 파일에 +229 −595입니다. 위의 이동 블록과 getMetaClassIdFromUrl을 지웠고, 로드가 끝나면 무조건 첫 노드를 고르게 했어요. 트리는 상세 페이지에서 목록 페이지로 옮겼습니다. 목록 템플릿이 62줄에서 158줄이 된 게 그겁니다.
목록 뷰가 상세 뷰 흉내를 내야 했습니다. context['object']에 첫 뿌리를 억지로 넣어 DetailView처럼 보이게 했고, try로 감쌀 이유가 없는데 감쌌습니다. 이유는 커밋에 없습니다.
이걸로 주소 하나에 트리와 표가 같이 살고, 노드 선택은 Ajax만 하게 됐습니다. 대신 특정 클래스를 가리키는 주소가 없어졌어요. 그건 닷새 뒤 ?selected_node=라는 쿼리 파라미터로 돌아옵니다.
첫 판이 무너진 자리
| 6월 10일 | 무너진 이유 | 6월 20일 |
|---|---|---|
| 템플릿 안 스크립트 550줄, 공개 화면에 한 벌 더 | 고칠 때마다 두 곳 | JS 파일 둘. 그래도 공통 함수는 두 벌 |
| 트리가 상세 페이지 안, 주소가 클래스 하나 | 노드 선택이 페이지 이동이 되면 트리를 매번 다시 그림 | 목록 페이지 하나, 선택은 Ajax |
| 주소 끝자리를 정규식으로 읽어 노드 선택 | 목록으로 옮기니 읽을 끝자리가 없음 | 삭제. 닷새 뒤 쿼리 파라미터로 대체 |
돌아보면 무너진 건 코드 양이 아니라 트리가 어느 페이지에 사는가였습니다. 상세 페이지에 두면 주소는 예쁘지만 클릭이 이동이 되고, 목록에 두면 클릭은 가벼운데 주소가 사라져요. 그때는 이 둘을 저울질한 게 아니라 하나를 해 보고 안 되니 다른 쪽으로 간 겁니다.
검증
- 세 커밋 전부 손으로 확인했습니다. 무엇을 눌러 봤는지는 기록이 없고, 자동화 테스트는 없었습니다.
- 이 글을 쓰면서 확인한 것: 세 커밋의 파일 수와 증감, 템플릿 네 개의 줄 수 변화(39→724→160, 39→213→92, 62→158, 63→93), 두 JS 파일에
getCookie·run_waitMe·ajaxSetup이 각각 한 벌씩 있다는 것. - 커밋 메시지는 첫 번째만 한글이고 나머지 둘은 영어 요약 문구라, 그날 무엇을 왜 했는지는 메시지가 아니라 diff에서 읽었습니다.
남은 것 · 한계
- 복사본은 그대로였습니다. 두 JS 파일의 공통 함수는 7월 17일에
commonClassTreeUtils.js로 모이기 시작했고, 옵션과 핸들러까지 하나로 합친 건 1년 가까이 지난 2026년 5월이었습니다. - 권한을 주소 문자열로 판단했습니다.
isAdmin()이web-admin이라는 글자를 찾는 함수예요. 서버는StaffOnlyMixin으로 막고 있으니 화면 표시용이긴 한데, 이 함수는 그 뒤 이름만checkAccessLevel로 바뀌어 살아남았습니다. | safe로 만든 JSON을 백틱 문자열에 넣었습니다. 값에 줄바꿈이 들어오면 문자열이 깨지는 구조인데, 그때는 몰랐고 1년 뒤 워드클라우드 화면에서 같은 패턴이 실제로 터졌습니다.console.log("동작 확인")은 하루 뒤 커밋에서 지웠지만, 하드코딩된 REST 주소와 편집 버튼 주소는 남았습니다.
관련 글: jsTree 기본 사용법 · 분류체계와 메타클래스 트리 공통 로직을 헬퍼 4종으로 추출한 회고 · 메타클래스 속성 편집을 모달에서 인라인 폼으로 옮긴 UX 회고 · 트리 뿌리를 이름으로 찾다가 id로 바꿨다 · handleAjaxError를 다시 고민했다