개발/Dash

[Dash]Python Dash Callback (4) 동적 데이터베이스 업데이트 및 복원

moonzoo 2024. 2. 13. 14:02

 

동적 데이터베이스 업데이트 및 복원

 

Action1. Modal 팝업창에서 '수정하기' 버튼을 클릭해 DB를 업데이트했다면, AG Grid의 데이터베이스를 다시 조회
Action2. Modal 팝업창에서 '닫기' 버튼을 클릭했을 때, 팝업창에서 내용을 수정했어도 기존의 상태로 복원

 

사용자가 모달에서 "수정하기" 버튼을 클릭했을 때 ag_grid_1데이터베이스를 업데이트하려면, toggle_modal_edit 콜백에서 데이터베이스 수정 로직을 완료한 후, ag_grid_1rowData를 새로 고칠 수 있도록 적절한 출력(Output)을 설정해야 합니다. 

 

toggle_modal_edit 콜백에서 데이터베이스 수정이 완료되었음을 나타내는 신호를 dcc.store를 통해 전달하여 update_grid 콜백의 입력(Input)으로 사용되어, ag_grid_1의 데이터를 새로고침했습니다.

 

 

dcc.Store(id='modal-data-store', storage_type='memory'), # 닫기 버튼 클릭시 복원을 위한 data store
dcc.Store(id='modal_edit_update_store'), # 수정하기 버튼 클릭 시 ag_grid 업데이트를 위한 data store

 

가장 먼저 Layout에 두 개의 dcc.store를 생성해줬습니다. 레이아웃의 위치는 어디에 두셔도 상관없습니다.

이제 각 컴포넌트를 사용해 두 가지 기능을 구현하겠습니다.

@app.callback(
    [Output('modal_edit', 'is_open'), Output('modal_edit_update_store', 'data')],
    [Input("modal_edit_open", "n_clicks"), Input("edit_close",
                                                 "n_clicks"), Input("edit_standard_set", "n_clicks")],
    [State('modal_edit', 'is_open'), State('modal_edit_intent_id', 'value'), State('modal_edit_brand_id', 'value'),
     State('modal_edit_intent_group', 'value'), State('modal_edit_question',
                                                      'value'), State('modal_edit_answer_type', 'value'),
     State('modal_edit_call_type', 'value'), State('modal_edit_button_label',
                                                   'value'), State('modal_edit_url_call', 'value'),
     State('modal_edit_intent_desc', 'value'), State('modal_edit_use_yn', 'value'), State('modal_edit_answer', 'value')]
)
def toggle_modal_edit(n1, n2, n3, is_open, edit_intent_id, edit_brand_id, edit_intent_group, edit_question,
                      edit_answer_type, edit_call_type, edit_button_label, edit_url_call,
                      edit_intent_desc, edit_use_yn, edit_answer):
    if n1 or n2 or n3:  # 순서대로 '표준셋수정하기(open)' 'Close', '수정하기'
        if n3:  # '수정하기' 버튼 클릭 시
            if not edit_question.strip() or not edit_answer.strip():
                raise PreventUpdate("질문과 답변 모두 입력해야 합니다.")
            # 데이터베이스 연결
            engine = create_engine(get_db_info())
            with engine.connect() as connection:
                # 데이터베이스에 추가하는 쿼리 실행
                query = text(f"""
                UPDATE db.dard_set 
                SET brand_id = '{edit_brand_id}', 
                    intent_group = '{edit_intent_group}', 
                    question = '{edit_question}', 
                    answer_type = '{edit_answer_type}', 
                    call_type = '{edit_call_type}', 
                    button_label = '{edit_button_label}', 
                    url_call = '{edit_url_call}', 
                    intent_desc = '{edit_intent_desc}', 
                    use_yn = '{edit_use_yn}', 
                    answer = '{edit_answer}'
                WHERE intent_id = '{edit_intent_id}'
                """)

                result = connection.execute(query)
                connection.commit()
            return not is_open, {'updated' : True} # 수정하기 버튼을 클릭하여 업데이트를 진행했을 때, updated를 True로 반환.
        return not is_open, dash.no_update
    return is_open, dash.no_update

 

콜백 데코레이터에서 Output에 dcc.store의 id인  modal_data_update_store와 속성 값을 data로 지정하겠습니다.

이를 통해 Input 요소에 해당하는 모달에 접근하는 버튼인 modal_edit_open = n1, 모달 내의 닫기 버튼인 edit_close = n2

모달내의 수정하기 버튼인 edit_standard_set = n3 이라는 3가지 입력 요소의 속성값이 변화할 때마다 dcc.store(id= modal_data_update_store)의 파라미터인 'updated'의 값을 다르게 반환합니다. 

 

- 수정하기 버튼(n3) 클릭(is_open)시 updated : True를 반환해 dcc.store에 업데이트를 진행합니다.

- 모달 접근하기 버튼 (n1), 모달 닫기 버튼(n2) 클릭 (is_open) 시 update를 진행하지 않고 dcc.store(id = modal-data-store)에 저장된 데이터로 복원합니다.

@app.callback(
    Output('ag_grid_1','rowData'),
    [Input('input_1','value'),Input('dropdown_1','value'),
     Input('input_2','value'),Input('dropdown_2','value'),
     Input('radio-ANDOR','value'),Input('checkbox-EM','value'),Input('checkbox-TR','value'),Input('checkbox-NB','value'),Input('checkbox-ZZ','value'),Input('checkbox-DD','value'),Input('modal_edit_update_store', 'data')]
)
def update_grid(input_value, dropdown_value, input_value2, dropdown_value2, radio_andor_value, checkbox1_value, checkbox2_value, checkbox3_value, checkbox4_value, checkbox5_value, update_store_data):
    if input_value is None:
        input_value = ''
    else:
        input_value = input_value.upper()
    
    if input_value2 is None:
        input_value2 = ''
    else:
        input_value2 = input_value2.upper()

    check_values=[]

    if checkbox1_value :
        check_values.append('A')
    if checkbox2_value :
        check_values.append('B')
    if checkbox3_value :
        check_values.append('C')
    if checkbox4_value :
        check_values.append('D')
    if checkbox5_value :
        check_values.append('E')

    # check_values가 비어 있으면 더미 값 '-1'을 사용
    if not check_values:
        check_values.append('-1')

    checkbox_values = ', '.join(f"'{item}'" for item in check_values)
    operator = radio_andor_value


    
    engine = create_engine(db_info)
    with engine.connect() as connection:
        query = text(
            f"SELECT intent_id, brand_id, intent_group, question, answer, use_yn, uid FROM bdb.set WHERE {dropdown_value} LIKE :x {operator} {dropdown_value2} LIKE :y AND brand_id IN ({checkbox_values}) ORDER BY uid ASC")
        result = connection.execute(
            query, {"x": '%' + input_value + '%', "y": '%' + input_value2 + '%'}).fetchall()

    # Convert the SQL result to the ag-Grid compatible format
    grid_data= [{"intent_id": row[0], "brand_id": row[1], "intent_group": row[2], "question": row[3], "answer": row[4], "use_yn": row[5]} for row in result]

    if update_store_data and update_store_data.get('updated'): # toggle_modal_edit에서 수정하기 버튼을 클릭했을 시 , updated가 True가 되면서 해당 코드를 실행해 ag_grid를 한번 더 업데이트 진행.
        return grid_data

    return grid_data

 

 

다음으로 앞서 레이아웃에 구성한 dcc.store의  id를 콜백 데코레이터의 Input에 추가해줍니다. 

속성값은 data를 저장할 것이기 때문에 data로 지정해줬습니다.

 

if update_store_data and update_store_data.get('updated'): # toggle_modal_edit에서 수정하기 버튼을 클릭했을 시 , updated가 True가 되면서 해당 코드를 실행해 ag_grid를 한번 더 업데이트 진행.
        return grid_data

 

Ag_grid를 업데이트 하는 update_grid 함수에 위의 코드를 추가하여, dcc.store(update_store_data)의 updated가 True일 경우 다시 Ag_grid를 업데이트 하는 로직을 추가합니다.

 

이는 앞서 모달의 업데이트를 관리하는 toggle_modal_edit 함수에서 모달의 수정하기 버튼(n3)이 클릭 됐을 시 dcc.store의 'updated'를 True로 반환하게 될 때, Ag_grid를 다시 한번 업데이트 합니다.

 

if triggered_id == 'edit_close': # '닫기' 버튼 클릭 시 dcc.store에 저장해둔 데이터 복원.
        if stored_data:
            return stored_data.get('intent_id', ''), stored_data.get('brand_id', ''), stored_data.get('intent_group', ''), stored_data.get('question', ''), stored_data.get('answer_type', ''),stored_data.get('call_type', ''),stored_data.get('button_label', ''),stored_data.get('url_call', ''),stored_data.get('intent_desc', ''),stored_data.get('use_yn', ''),stored_data.get('answer', ''),stored_data
        return '', '', '', '', '', '', '', '', '', '', '',{}

 

반대로 모달의 닫기 버튼 클릭 시 dcc.store(id = modal-data-store)에 저장된 데이터로 ag_grid를 복원합니다.

 

결론

이를 통해 데이터베이스를 실시간으로 업데이트하고, 웹에서 실시간으로 반응하는 기능을 구현했습니다.