Skip to content

fix: 위치 데이터가 수정 페이지에 보이지 않고 변동없을 경우 반영이 안되는 이슈 해결#148

Merged
recoma96 merged 1 commit into
mainfrom
fix/admin-place-coordinate-error
Apr 11, 2026
Merged

fix: 위치 데이터가 수정 페이지에 보이지 않고 변동없을 경우 반영이 안되는 이슈 해결#148
recoma96 merged 1 commit into
mainfrom
fix/admin-place-coordinate-error

Conversation

@recoma96
Copy link
Copy Markdown
Owner

@recoma96 recoma96 commented Apr 11, 2026

📝 주요 변경 내용 (Description)

문제 상황

PlaceAdmin 수정 페이지(/admin/place/edit/{id})에서 다음과 같은 문제가 발생했습니다.

  1. 기존 위경도 데이터가 표시되지 않음: DB에 geom/geog 값이 실제로 존재함에도 불구하고, 수정 페이지의 위경도 입력 필드(geo)는 비어 있는 상태로 렌더링됨.
  2. 위경도 변동 없이 저장 시 에러 발생: 위경도를 수정하지 않고 그대로 "Save" 버튼을 누르면 geomNULL이 되어 저장에 실패함.

동일한 패턴을 사용하는 KmaMountainForecastAreaAdmin도 같은 문제를 가지고 있었습니다.

원인 분석

1. 수정 페이지에서 기존 위경도가 표시되지 않은 이유

PlaceAdmin은 실제 컬럼 geom/geogform_excluded_columns로 제외하고, scaffold_form에서 가상 필드 geo (StringField)를 동적으로 추가하는 구조입니다.

sqladmin은 수정 페이지 렌더링 시 Form(obj=model, data=...) 형태로 wtforms 폼을 생성하는데(sqladmin/application.py:578), wtforms는 각 필드를 getattr(obj, field_name)으로 채웁니다. 그런데 model(Place 인스턴스)에는 geo라는 속성이 존재하지 않으므로 필드는 항상 빈 값으로 렌더링되었습니다.

2. 위경도 변동 없이 저장할 때 에러가 발생한 이유

기존 on_model_change 구현은 다음과 같았습니다.

async def on_model_change(self, data, model, is_created, request) -> None:                                                                                                                                                                                                                                                                                
    geo_string = data.get("geo")
    if not geo_string:                                                                                                                                                                                                                                                                                                                                      
        return  # 여기서 바로 return → data["geo"] = "" 가 그대로 남음
                                                                                                                                                                                                                                                                                                                                                            
    ...                                                                                                                                                                                                                                                                                                                                                   
    data.pop("geo", None)                                                                                                                                                                                                                                                                                                                                   

geo가 비어 있는 경우 data.pop("geo", None)을 하지 않고 조기 return 하기 때문에, data 딕셔너리에는 여전히 {"geo": ""}가 남은 채로 sqladmin 내부 로직으로 흘러 들어갑니다.

sqladmin의 _set_attributes_async(sqladmin/_queries.py:107)는 다음과 같이 동작합니다.

for key, value in data.items():                                                                                                                                                                                                                                                                                                                             
   column = self.model_view._mapper.columns.get(key)  # 'geo'는 실제 컬럼이 아니므로 None                                                                                                                                                                                                                                                                
   relation = self.model_view._mapper.relationships.get(key)                                                                                                                                                                                                                                                                                               

   if not value:                                                                                                                                                                                                                                                                                                                                           
       if is_falsy_value(value) and not relation and column.nullable:                                                                                                                                                                                                                                                                                    
           value = None                                                                                                                                                                                                                                                                                                                                    
       setattr(obj, key, value)                                                                                                                                                                                                                                                                                                                            
       continue                                                                                                                                                                                                                                                                                                                                            

geo는 SQLAlchemy 매퍼에 등록된 실제 컬럼이 아니므로 column은 None이고, 빈 문자열이 들어와 조건 분기에서 column.nullable에 접근하면서 문제가 됩니다. 결과적으로 실제 NOT NULL 컬럼인 geom/geog은 form에서 제외되어 있어 갱신되지 않은 채, 저장 경로가 꼬여 최종적으로 geom이 NULL인 상태로 커밋이 시도되어 에러가 발생했습니다.

수정 내용

  1. 수정 페이지에 현재 위경도 주입 (get_object_for_edit 오버라이드)

sqladmin이 수정 페이지용 모델을 로드하는 get_object_for_edit를 오버라이드하여, 기존 geom 값을 "lat, lng" 문자열 형태로 obj.geo에 주입합니다. 이렇게 하면 이후 Form(obj=model)에서 wtforms가 getattr(obj, "geo")를 호출할 때 현재 위경도가 자연스럽게 채워집니다.

  async def get_object_for_edit(self, request: Request) -> Any:                                                                                                                                                                                                                                                                                               
      obj = await super().get_object_for_edit(request)                                                                                                                                                                                                                                                                                                      
      if obj is not None and getattr(obj, "geom", None) is not None:                                                                                                                                                                                                                                                                                          
          point = to_shape(obj.geom)                                                                                                                                                                                                                                                                                                                          
          obj.geo = f"{point.y}, {point.x}"
      return obj                                                                                                                                                                                                                                                                                                                                              
  1. on_model_change에서 geo를 항상 pop 하도록 수정

가상 필드 geo가 sqladmin의 setattr 루프에 절대 도달하지 못하도록, 조건 분기 이전에 항상 data.pop("geo", None)을 수행하도록 변경했습니다. 값이 비어 있으면서 수정(edit) 모드인 경우에는 기존 geom/geog 값을 그대로 유지하도록 early return 하고, 생성(create) 모드인 경우에는 명확한 400 에러를 반환합니다.

  async def on_model_change(self, data, model, is_created, request) -> None:                                                                                                                                                                                                                                                                                  
      # 가상 필드 'geo'는 실제 컬럼이 아니므로, sqladmin의 setattr 루프에                                                                                                                                                                                                                                                                                     
      # 도달하기 전에 반드시 data에서 제거해야 합니다.                                                                                                                                                                                                                                                                                                        
      geo_string = data.pop("geo", None)                                                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                                                              
      if not geo_string:                                                                                                                                                                                                                                                                                                                                    
          # 값이 없으면 기존 위경도 데이터를 그대로 유지합니다.
          if is_created:                                                                                                                                                                                                                                                                                                                                      
              raise HTTPException(
                  status_code=HTTP_400_BAD_REQUEST,                                                                                                                                                                                                                                                                                                           
                  detail="위경도 데이터가 필요합니다."                                                                                                                                                                                                                                                                                                        
              )
          return                                                                                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                                                                                            
      lat, lon = parse_string_to_lat_lng(geo_string)                                                                                                                                                                                                                                                                                                          
      point_element = parse_location_to_wkt(lat, lon)
      model.geog = model.geom = point_element                                                                                                                                                                                                                                                                                                                 
  1. 동일 패턴을 공유하는 KmaMountainForecastAreaAdmin에도 동일 수정 적용

PlaceAdmin과 완전히 동일한 구조(geom/geog 제외 + 가상 geo 필드)를 갖고 있어 같은 버그가 잠재되어 있었으므로, 두 어드민 클래스에 모두 동일한 수정을 적용했습니다.

동작 확인

  • 수정 페이지 진입 시 geo 입력 필드에 기존 위경도가 "37.xxxxxx, 127.xxxxxx" 형식으로 표시된다.
  • 위경도를 변경하지 않고 "Save"를 눌러도 정상적으로 저장된다 (기존 geom/geog 유지).
  • 위경도를 변경하고 저장하면 새 좌표로 갱신된다.
  • 신규 생성 시 위경도 미입력이면 400 에러와 함께 명확한 메시지가 반환된다.

🔗 관련 이슈 (Related Issues)

수정 페이지 진입 시, 위경도 데이터를 가상 필드인 geo에 넣어서 보이게 설정
모델 변경 시, geo에 있는 위경ㄷ 데이터를 뽑아 DB에 저장하는 방식으로 진행
추가로 코스 구간에 대해 print를 할 경우 PlaceID가 아닌 순수 구간 이름으로 나타낼 수 있게 수정
@recoma96 recoma96 merged commit df6b796 into main Apr 11, 2026
1 check passed
@recoma96 recoma96 deleted the fix/admin-place-coordinate-error branch April 11, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[관리자페이지] Place 정보 변경 시 위치데이터 초기화 이슈

1 participant