-
Python: 간단한 streamlit 앱 만들면서 배운 점컴퓨터/파이썬 2021. 9. 19. 14:34728x90반응형
streamlit
만든 것
데이터 앱을 쉽고 예쁘게 만들 수 있는 streamlit,
mongoDB 필터링 기능도 익힐 겸 아래의 값들에 따라 필터링을 해서 테이블을 업데이트한다.
(자동으로 공지들을 db에 저장하고 있다.)
구조
처음에 st.slider, st.multiselect 와 같은 위젯들 on_change에 callback 함수를 추가해서
그 함수에서 값들을 받고, 필터를 만들고, global 테이블을 업데이트했다.
st.title("title") my_table = st.empty() def update_notice(): global my_table global categories my_table.empty() filter = {} try: # whenever I change category, it is changed too if categories: filter = {"$or": [{"category": category} for category in categories]} except NameError: pass sort = list({"id": -1}.items()) # I tried same method like categories for count, but the count value never changes result = tuple( client["db"]["collection"].find(filter=filter, sort=sort, limit=count) ) # print(result) data = { "title": tuple(article["title"] for article in result), } df = pd.DataFrame(data) df.index = [f"{i}th" for i in range(1, len(result) + 1)] my_table.table(df) categories = st.multiselect( "label", categories, # like ["a", "b"...] [], on_change=update_notice(), # I should pass the function not return value, but the table disappears when I use lambda: f() or f... help="help", ) count = st.slider( "label", min_value=1, max_value=100, value=10, on_change=update_notice(), # I should pass the function not return value, but the table disappears... help="help", ) # print(count), if I print it in here, it prints updated value..
하지만 이런 방식은 테이블이 무조건 사라지고 값들이 추가되어 뚝뚝 끊기는 업데이트 느낌과
2~3번 중복으로 업데이트되었고, 위젯들 값들도 받기가 어려웠다.
streamlit 커뮤니티에 질문했더니 streamlit은 위젯들 값이 바뀔 때 코드를 처음부터 끝까지 다시 실행한다고 한다.
그래서 떠올린 방법은 각 위젯들에게 key를 부여하고 st.session_state.some_key 를 확인하여
처음 실행할 때 필터 오류를 방지했다. (그러면 on_change는 중간 인터셉터 역할?)
처음: 각 위젯 on_change에 callback 함수 추가 -> callback에서 위젯 값들 받아오고 table 업데이트 시도
이후: 처음에 state 확인하고 몽고DB 필터 만들기 -> table 다시 생성
mongoDB 필터filter = {"$and": [{"$or": []}]} sort_id = list({"id": -1}.items()) client["db"]["collection"].find( filter=filter, sort=sort_id, limit=10 ) # range 필터 # db 각 struct 마다 date가 "21.07.21"처럼 있음 # greater equal, less than equal (inclusive) # 아래는 오늘 ~ 3일전 filter["$and"].append( { "date": { "$gte": (datetime.now() - timedelta(days=3)).strftime("%y.%m.%d"), "$lte": datetime.now().strftime("%y.%m.%d"), } } ) # regex filter["$and"].append({"title": {"$regex": "hello"}}) # or filter["$and"][0]["$or"].append({"category": cate})
이미지 centering
st.markdown( f""" <div style="display: flex; justify-content: center"> <p> <a href="link" target="_blank" rel="noopener"><img style="width: 100px" src="https://user-images.githubusercontent.com/2356749/133914037-2b5e515a-3ac6-4228-b7ea-294d8fa735f1.png"></a> </p> """, unsafe_allow_html=True, )
메뉴 + Made with streamlit 없애기 (바꾸기)
# 버거 메뉴 + footer 수정 hide_streamlit_style = """ <style> #MainMenu {visibility: hidden; } footer {visibility: hidden;} footer:after {visibility: visible; content:"footer!";} </style> """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)
코드
from datetime import datetime, timedelta from os import linesep import pandas as pd import streamlit as st from pymongo import MongoClient MONGODB = "mongodb+srv://DB주소" client = MongoClient(MONGODB, serverSelectionTimeoutMS=5000) filter = {"$and": [{"$or": []}]} sort_id = list({"id": -1}.items()) result = [] min_date, max_date = datetime.now() - timedelta(days=3), datetime.now() ajou_categories = [ "학사", "비교과", "장학", "학술", "입학", "취업", "사무", "기타", "행사", "파란학기제", "학사일정", ] # 필터링 값 만들기 def collect_selections(): global filter, result, min_date, max_date try: min_date, max_date = st.session_state.my_date except Exception: pass try: if st.session_state.my_category: for category in st.session_state.my_category: filter["$and"][0]["$or"].append({"category": category}) # filter = {"$or": [{"category": category} for category in categories]} except Exception: pass try: filter["$and"].append( { "date": { "$gte": st.session_state.my_date[0].strftime("%y.%m.%d"), "$lte": st.session_state.my_date[1].strftime("%y.%m.%d"), } } ) # pyright: reportGeneralTypeIssues=false except Exception: filter["$and"].append( { "date": { "$gte": (datetime.now() - timedelta(days=3)).strftime("%y.%m.%d"), "$lte": datetime.now().strftime("%y.%m.%d"), } } ) try: filter["$and"].append({"title": {"$regex": st.session_state.my_keyword}}) except Exception: pass if not filter["$and"][0]["$or"]: filter["$and"][0]["$or"].append({}) try: result = tuple( client["ajou"]["notice"].find( filter=filter, sort=sort_id, limit=st.session_state.my_count ) ) except AttributeError: result = tuple( client["ajou"]["notice"].find(filter=filter, sort=sort_id, limit=10) ) collect_selections() # 로고 img 센터링 st.markdown( f""" <div style="display: flex; justify-content: center"> <p> <a href="https://www.ajou.ac.kr/kr/ajou/notice.do" target="_blank" rel="noopener"><img style="width: 100px" src="https://user-images.githubusercontent.com/2356749/133914037-2b5e515a-3ac6-4228-b7ea-294d8fa735f1.png"></a> </p> """, unsafe_allow_html=True, ) data = { "제목": tuple(notice["title"] for notice in result), "날짜": tuple(notice["date"] for notice in result), "글쓴이": tuple(notice["writer"] for notice in result), "종류": tuple(notice["category"] for notice in result), "링크": tuple(notice["link"] for notice in result), } # 마크다운 테이블에 쓸 text 생성 markdown_txt = [] for i in range(len(result)): markdown_txt.append( f'|[{data["제목"][i]}]({data["링크"][i]})|{data["날짜"][i]}|{data["글쓴이"][i]}|{data["종류"][i]}|' ) st.markdown( f""" |제목|날짜|글쓴이|종류| |:---:|:---:|:---:|:---:| {linesep.join(markdown_txt)} """ ) st.write(linesep) # little blank if len(result) == 0: st.error("조건에 맞는 공지가 없습니다!") keyword = st.text_input( "공지 키워드를 입력하세요.", key="my_keyword", max_chars=30, help="공지 제목을 필터링할 수 있습니다.", ) categories = st.multiselect( "카테고리 선택", ajou_categories, [], key="my_category", help="공지 카테고리를 선택할 수 있습니다.", ) notice_date = st.slider( "공지 날짜", min_value=datetime.now() - timedelta(days=30), max_value=datetime.now(), value=(min_date, max_date), format="MM/DD", key="my_date", help="공지 날짜를 지정할 수 있습니다.", ) count = st.slider( "공지 개수", min_value=1, max_value=100, value=10, key="my_count", help="공지 개수를 지정할 수 있습니다.", ) # 버거 메뉴 + footer 수정 hide_streamlit_style = """ <style> #MainMenu {visibility: hidden; } footer {visibility: hidden;} footer:after {visibility: visible; content:"My Footer!";} </style> """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)
728x90'컴퓨터 > 파이썬' 카테고리의 다른 글
Python: 사진 to pdf 파일 변환 (0) 2021.10.23 FastAPI + discord.py snippet (0) 2021.08.24 Python Selenium: 여러가지 팁 (0) 2021.06.07