- 발행일
저장 뒤 돌아올 자리를 주소에 남겼다 — ?selected_node=의 첫 판
저장 뒤 돌아올 자리를 주소에 남겼다
이 글은 2026년 9월에 당시 커밋 이력을 다시 보며 정리한 것입니다. 날짜는 작업한 날 기준입니다.
닷새 전에 메타데이터 클래스 트리를 상세 페이지에서 목록 페이지로 옮겼습니다. 주소 하나에 트리와 속성 표가 같이 살고, 노드를 고르면 Ajax로 표만 바뀌는 구조예요. 가벼워진 대신 특정 클래스를 가리키는 주소가 없어졌습니다.
그게 바로 걸렸습니다. 속성 편집은 별도 폼 화면이라, 저장하면 어딘가로 돌아가야 해요. 그때 성공 주소는 속성 상세였습니다.
def get_success_url(self):
return reverse_lazy(
"web-admin:std-data:meta_class_property_detail", kwargs={"pk": self.kwargs["pk"]}
)
거기서 "목록" 버튼을 누르면 트리 화면인데, 트리는 첫 노드가 골라진 채로 뜹니다. 편집하던 클래스가 트리 아래쪽에 있으면 다시 찾아 내려가야 했어요. 6월 20일 커밋에는 이런 주석이 남아 있습니다.
# TODO : 지금은 단순히 뒤로가기 중
class MetaClassPropertyDetailView(StaffOnlyMixin, DetailView):
성공 주소에 클래스 id를 실었다
6월 25일 커밋(e7b15d1b)은 두 파일 27줄입니다. 서버 쪽은 성공 주소를 바꿨어요.
# TODO : 메타 클래스 ID를 쿼리 파라미터로 전달
def get_success_url(self):
class_id = self.object.metaclass.id
return reverse_lazy("web-admin:std-data:meta_class_list") + f"?selected_node={class_id}"
속성이 딸린 클래스의 id를 ?selected_node=에 실어 목록으로 보냅니다. 바로 위 TODO 주석은 이 함수가 하는 일을 그대로 적은 건데, 할 일을 적고 나서 그 자리에서 해 버리고 주석은 안 지운 모양새예요.
프런트 쪽은 그 값을 읽어 로드가 끝나면 골라 줍니다.
function getSelectedNodeIdFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('selected_node');
}
.on("loaded.jstree", function () {
treeEl.jstree("open_all");
const nodeId = getSelectedNodeIdFromUrl();
if (nodeId) {
setTimeout(function() {
treeEl.jstree("select_node", nodeId);
}, 300);
} else {
treeEl.jstree("select_node", "ul > li:first");
}
})
open_all 뒤에 300ms를 기다렸다가 고릅니다. 왜 300인지는 커밋에 없어요. open_all이 끝나기 전에 select_node를 부르면 안 될 것 같아서 넣은 것으로 보이는데, jsTree의 select_node는 부모가 닫혀 있어도 _open_to로 알아서 열어 주는 함수라 open_all을 기다릴 이유가 없었습니다. 그건 그때 몰랐습니다.
프런트에서는 URLSearchParams나 쿼리로 상태를 남기는 게 익숙한 일이라 이쪽은 망설임이 없었어요. 망설인 건 Django 쪽이었습니다. reverse_lazy 결과에 문자열을 붙여도 되는지, self.object.metaclass가 저장 뒤에 채워져 있는지 같은 것들이요. 결과적으로 됐고, 왜 되는지는 나중에 알았습니다.
이틀 뒤 저녁, 타이밍을 덮으려다 두 시간 만에 지웠다
6월 27일 18:31 커밋(c40614ab)은 트리 화면의 다른 문제를 만지던 것입니다. 클래스를 추가하거나 수정한 직후 표가 옛 값으로 그려지는 증상이 있었어요. 거기에 세 가지를 넣었습니다.
// 캐시 이슈 해결 방법
if (settings.type === "GET") {
xhr.setRequestHeader("Cache-Control", "no-cache, no-store, must-revalidate");
xhr.setRequestHeader("Pragma", "no-cache");
xhr.setRequestHeader("Expires", "0");
}
GET마다 캐시 금지 헤더를 붙이고, 추가·수정 성공 뒤의 표 갱신을 setTimeout(..., 100)으로 감싸고, 전역 URL 변수 이름을 정리했습니다. 이름 정리는 metaListUrl을 metaClassListUrl로, 원래 metaClassListUrl이던 것을 metaClassPropertyListUrl로 바꾼 거라, 같은 이름이 다른 뜻이 되는 교체였어요.
20:44 커밋(fbb1550e)이 이걸 다 걷어냈습니다. 캐시 헤더 삭제, 100ms 삭제, 그리고 ?selected_node=의 300ms도 삭제. 대신 이걸 넣었습니다.
// 페이지 새로고침 함수
// 트리와 테이블 데이터의 일관성을 보장하기 위해 전체 페이지 새로고침 사용
function refreshTreeData() {
window.location.reload();
}
추가·수정·삭제가 끝나면 refreshTreeData(), 즉 페이지를 새로고침합니다. 두 시간 사이에 "기다리면 맞겠지"에서 "다시 그리면 맞겠지"로 간 거예요. 둘 다 옛 값이 왜 그려지는지는 찾지 않은 채였습니다. 커밋 제목은 "optimize tree and table refresh logic"인데, 지금 보면 최적화가 아니라 우회입니다.
그래도 ?selected_node= 자체는 살아남았습니다. 새로고침을 해도 주소에 클래스 id가 있으니 같은 자리로 돌아오거든요. 타이밍 코드를 걷어내고 나니 이 파라미터가 오히려 새로고침 방식을 받쳐 주는 구조가 됐습니다.
검증
- 속성을 편집하고 저장한 뒤 트리에서 그 클래스가 골라진 채로 돌아오는 것을 손으로 확인했습니다.
- 300ms를 뺀 커밋이 그대로 남은 걸 보면 뺀 뒤에도 동작했을 겁니다. 다만 그건 "빼도 된다"까지이지 "왜 필요 없는지"를 안 건 아니었어요.
- 캐시 헤더가 실제로 문제를 일으켰는지, 없앴을 때 무엇이 달라졌는지는 기록이 없습니다. 두 시간 뒤 지운 걸로 보아 효과가 없었다고 판단한 것 같은데, 그 판단의 근거는 커밋에 없습니다.
남은 것 · 한계
- 옛 값이 그려지던 원인을 못 찾고 새로고침으로 덮었습니다. 1년 뒤 편집 중 트리를 누르면 입력이 사라지던 문제를 잡으면서 jsTree
refresh()가 비동기라는 걸 알았는데, 이때의 증상도 그 근처였을 가능성이 있습니다. 확인하지는 못했어요. TODO주석 둘이 한 일 위에 남았습니다. "메타 클래스 ID를 쿼리 파라미터로 전달"은 이미 전달하고 있고, "지금은 단순히 뒤로가기 중"은 이 커밋으로 뒤로가기가 아니게 됐는데 둘 다 안 지웠습니다.- 주소를 이력에 어떻게 쌓을지는 생각하지 않았습니다. 성공 주소에 파라미터를 붙였을 뿐, 트리에서 노드를 고를 때 주소를 바꾸지는 않았어요. 그래서 뒤로가기로 돌아오면 여전히 첫 노드였습니다. 이 파라미터가
replaceState로 트리 선택마다 주소에 실리고, 다른 화면으로 나갔다 돌아와도 살아남게 된 건 2026년 9월입니다. 그 글에서 "용어 트리와 OID 트리는 이미?selected_node=를 남기고 있었다"고 적은 그 파라미터가 이날 만든 겁니다. - 속성 편집이 별도 화면이라는 전제 자체는 이듬해 인라인 폼으로 바뀌면서 사라졌습니다. 돌아올 자리를 남길 필요가 없어진 거예요.
관련 글: 링크를 따라간 뒤 돌아오는 길이 어긋났다 · 편집 중 트리를 누르면 입력이 사라졌다 · 메타클래스 속성 편집을 모달에서 인라인 폼으로 옮긴 UX 회고 · 트리 뿌리를 이름으로 찾다가 id로 바꿨다 · 트리에서 뺀 신청 상태 표시를 일주일 뒤 되살렸다