commit 5caf16bb9c298c6919616bc8f2611de6c826acb6 Author: attojeon Date: Tue Nov 14 20:50:38 2023 +0900 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39901df --- /dev/null +++ b/.gitignore @@ -0,0 +1,132 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +dist/ +eggs/ +*.egg-info/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.coverage +.tox/ +.nox/ +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# CFFI +.cffi_cache/ + +# pycache +__pycache__/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5b6b2e4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // IntelliSense를 사용하여 가능한 특성에 대해 알아보세요. + // 기존 특성에 대한 설명을 보려면 가리킵니다. + // 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요. + "version": "0.2.0", + "configurations": [ + { + "name": "Python: 현재 파일", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: Streamlit", + "type": "python", + "request": "launch", + "module": "streamlit", + "justMyCode": true, + "args": ["run", "${file}"], + "env": { + "STATION_BUS_API_KEY1": "jEN2Laz4w1" + } + } + ] +} diff --git a/main.py b/main.py new file mode 100644 index 0000000..006ff27 --- /dev/null +++ b/main.py @@ -0,0 +1,160 @@ +############################ +# 데이터베이스 사용 이해하기 +############################ + +# from tinydb import TinyDB, Query +import pandas as pd +from datetime import datetime +from pprint import pprint +from userdblib import fetch_user_by_email, fetch_user_by_id, fetch_users, insert_user, update_user, delete_user, create_table, get_new_id + +# # 데이터베이스 생성 및 연결 +# db = TinyDB('db.json') + + +# ########################################### +# # 데이터베이스 쿼리 함수들 # +# ########################################### + +# # 데이터가 없으면 샘플 데이터 추가 +# def create_table(): +# if len(db.all()) == 0: +# users = [ +# {'id': 1, 'name': '전 John', 'email': 'john@gmail.com', 'age': 20, 'created_at': '2021-01-01 00:00:00'}, +# {'id': 2, 'name': '오 Jane', 'email': 'jane@gmail.com', 'age': 25, 'created_at': '2021-01-02 00:00:00'}, +# {'id': 3, 'name': '방 Bob', 'email': 'bob@gmail.com', 'age': 30, 'created_at': '2021-01-03 00:00:00'}, +# {'id': 4, 'name': '윤 Alice', 'email': 'alice@gmail.com', 'age': 35, 'created_at': '2021-01-04 00:00:00'}, +# {'id': 5, 'name': '김 Bill', 'email': 'bill@gmail.com', 'age': 40, 'created_at': '2021-01-05 00:00:00'}, +# ] +# db.insert_multiple(users) + +# def get_new_id(): +# users = fetch_users() +# new_id = users[-1]['id'] + 1 +# return new_id + +# def fetch_users(): +# return db.all() + +# def fetch_user_by_id(id): +# users = db.search(Query().id == id) +# if len(users) == 0: +# return [] +# else: +# return users.pop() + +# def fetch_user_by_email(email): +# users = db.search(Query().email == email) +# if len(users) == 0: +# return [] +# else: +# return users.pop() + +# def insert_user(user): +# # new_user = user_from_input() +# db.insert(user) + +# def update_user(user): +# db.update(user, Query().id == user['id']) + +# def delete_user(id): +# db.remove(Query().id == id) + + + + +########################################### +# 화면 기능 함수들 # +########################################### + +def user_from_input(): + print("#"*30 + " 사용자 정보 입력 " + "#"*30) + name = input("이름: ") + email = input("이메일: ") + age = int(input("나이: ")) + created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + user = {'id': get_new_id(), 'name': name, 'email': email, 'age': age, 'created_at': created_at} + return user + +def user_from_input_update(user): + print("#"*30 + " 사용자 정보 업데이트 " + "#"*30) + name = input(f"이름({user['name']}): ") + email = input(f"이메일({user['email']}): ") + age = int(input(f"나이({user['age']}): ")) + # created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + user = {'id': user["id"], 'name': name, 'email': email, 'age': age, 'created_at': user["created_at"]} + return user + +# Function to display all users +def display_users(): + users = fetch_users() + for user in users: + print(f"ID:{user['id']}\n이름:{user['name']}\n전자메일:{user['email']}\n나이:{user['age']}\n가입날짜:{user['created_at']}\n") + +def add_new_user(): + user = user_from_input() + insert_user(user) + print("새 사용자가 추가되었습니다.") + +def update_existing_user(): + print("#"*30 + " 사용자 정보 수정 " + "#"*30) + id = int(input("수정할 사용자의 ID를 입력하세요: ")) + user = fetch_user_by_id(id) + if len(user) == 0: + print("해당 ID의 사용자가 없습니다.") + else: + user = user_from_input_update(user) + user['id'] = id + update_user(user) + print("사용자 정보가 수정되었습니다.") + +def delete_existing_user(): + print("#"*30 + " 사용자 정보 삭제 " + "#"*30) + id = int(input("삭제할 사용자의 ID를 입력하세요: ")) + user = fetch_user_by_id(id) + if len(user) == 0: + print("해당 ID의 사용자가 없습니다.") + else: + delete_user(id) + print("사용자 정보가 삭제되었습니다.") + +def search_user_by_email(): + print("#"*30 + " 사용자 정보 검색 " + "#"*30) + email = input("검색할 사용자의 이메일을 입력하세요: ") + user = fetch_user_by_email(email) + if len(user) == 0: + print("해당 이메일의 사용자가 없습니다.") + else: + print(f"ID:{user['id']}\n이름:{user['name']}\n전자메일:{user['email']}\n나이:{user['age']}\n가입날짜:{user['created_at']}\n") + +########################################### + + +# main 함수 +create_table() +while True: + print("#"*30 + " 사용자 관리 프로그램 " + "#"*30) + print("1. 사용자 전체 조회") + print("2. 사용자 추가") + print("3. 사용자 수정") + print("4. 사용자 삭제") + print("5. 이메일로 사용자 검색") + print("6. 종료") + print("#"*80) + menu = int(input("메뉴를 선택하세요: ")) + if menu == 1: + display_users() + elif menu == 2: + add_new_user() + elif menu == 3: + update_existing_user() + elif menu == 4: + delete_existing_user() + elif menu == 5: + search_user_by_email() + elif menu == 6: + print("프로그램을 종료합니다.") + break + else: + print("잘못된 메뉴입니다. 다시 선택하세요.") + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..7d1de1c --- /dev/null +++ b/readme.md @@ -0,0 +1,39 @@ +[]: # BEGIN: 1c3f3c8f7c7d +# 데이터베이스와 스트림잇 웹앱! + * 스트림잇 기반의 웹앱 프로젝트를 수행하면서 + +## main.py 미리보기 + * 터미널환경으로 구현한 '사용자관리'하기 + + + +## webapp.py + * 웹앱으로 구현한 '사용자관리' 프로그램 + + + +## userdblib.py + * tinydb를 직접 액세스하는 라이브러리 모듈 + * 기본적인 CRUD 기능 구현 + * fetch + * update + * delete + * create + +## userdb.json + * 사용자DB 파일 + * tinydb 형식으로 저장된 json데이터베이스 + * utf-8 인코딩 자체적으로 사용함. + * 직접 파일을 수정하는 것은 권하지 않음. + * 참조 https://github.com/msiemens/tinydb.git + +## 설치 +```bash + pip install streamlit --upgrade +``` \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d77d650 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +streamlit +tinydb \ No newline at end of file diff --git a/streamlit_tinydb1_1.mp4 b/streamlit_tinydb1_1.mp4 new file mode 100644 index 0000000..92d1426 Binary files /dev/null and b/streamlit_tinydb1_1.mp4 differ diff --git a/streamlit_tinydb1_2.mp4 b/streamlit_tinydb1_2.mp4 new file mode 100644 index 0000000..7eba57f Binary files /dev/null and b/streamlit_tinydb1_2.mp4 differ diff --git a/userdb.json b/userdb.json new file mode 100644 index 0000000..9d966a1 --- /dev/null +++ b/userdb.json @@ -0,0 +1 @@ +{"_default": {"1": {"id": 1, "name": "\uc804\uc0c1\ud6042", "email": "john@gmail.com", "age": 22, "created_at": "2021-01-01 00:00:00"}, "2": {"id": 2, "name": "\uc624 Jane", "email": "jane@gmail.com", "age": 25, "created_at": "2021-01-02 00:00:00"}, "3": {"id": 3, "name": "\ubc29 Bob", "email": "bob@gmail.com", "age": 30, "created_at": "2021-01-03 00:00:00"}, "4": {"id": 4, "name": "\uc724 Alice", "email": "alice@gmail.com", "age": 35, "created_at": "2021-01-04 00:00:00"}, "15": {"id": 15, "name": "\uc804\uc0c1\ud604", "email": "john@gmail.com", "age": 20, "created_at": "2023-11-09 17:47:20"}, "16": {"id": 16, "name": "\uc724\uc2e0\uc6d0", "email": "yoon@gmail.com", "age": 40, "created_at": "2023-11-09 18:58:49"}, "17": {"id": 17, "name": "\ud64d\uae38\ub3d9", "email": "hong@korea.com", "age": 29, "created_at": "2023-11-09 18:59:09"}, "18": {"id": 18, "name": "\uc720\uad00\uc21c", "email": "gw.ryu@korea.net", "age": 24, "created_at": "2023-11-09 18:59:38"}, "19": {"id": 19, "name": "\uae40\uad6c", "email": "goo.kim@korea.com", "age": 50, "created_at": "2023-11-09 20:18:45"}, "21": {"id": 21, "name": "\uc138\uc885\ub300\uc655", "email": "kingofchosun@korea.com", "age": 30, "created_at": "2023-11-10 10:45:54"}, "22": {"id": 22, "name": "\uac15\uac10\ucc2c", "email": "kang.gc@korea.com", "age": 50, "created_at": "2023-11-10 10:58:53"}}} \ No newline at end of file diff --git a/userdblib.py b/userdblib.py new file mode 100644 index 0000000..cfa3df8 --- /dev/null +++ b/userdblib.py @@ -0,0 +1,54 @@ +from tinydb import TinyDB, Query +from datetime import datetime +from pprint import pprint +# 데이터베이스 생성 및 연결 +db = TinyDB('userdb.json') +########################################### +# 데이터베이스 쿼리 함수들 # +########################################### + +# 데이터가 없으면 샘플 데이터 추가 +def create_table(): + if len(db.all()) == 0: + users = [ + {'id': 1, 'name': '전 John', 'email': 'john@gmail.com', 'age': 20, 'created_at': '2021-01-01 00:00:00'}, + {'id': 2, 'name': '오 Jane', 'email': 'jane@gmail.com', 'age': 25, 'created_at': '2021-01-02 00:00:00'}, + {'id': 3, 'name': '방 Bob', 'email': 'bob@gmail.com', 'age': 30, 'created_at': '2021-01-03 00:00:00'}, + {'id': 4, 'name': '윤 Alice', 'email': 'alice@gmail.com', 'age': 35, 'created_at': '2021-01-04 00:00:00'}, + {'id': 5, 'name': '김 Bill', 'email': 'bill@gmail.com', 'age': 40, 'created_at': '2021-01-05 00:00:00'}, + ] + db.insert_multiple(users) + +def get_new_id(): + users = fetch_users() + new_id = users[-1]['id'] + 1 + return new_id + +def fetch_users(): + return db.all() + +def fetch_user_by_id(id): + users = db.search(Query().id == id) + if len(users) == 0: + return [] + else: + return users.pop() + +def fetch_user_by_email(email): + users = db.search(Query().email == email) + if len(users) == 0: + return [] + else: + return users.pop() + +def insert_user(user): + # new_user = user_from_input() + db.insert(user) + +def update_user(user): + db.update(user, Query().id == user['id']) + +def delete_user(id): + db.remove(Query().id == id) + + diff --git a/webapp.py b/webapp.py new file mode 100644 index 0000000..1c7b0c0 --- /dev/null +++ b/webapp.py @@ -0,0 +1,138 @@ +import pandas as pd +from datetime import datetime +from pprint import pprint +import streamlit as st +from userdblib import fetch_user_by_email, fetch_user_by_id, fetch_users, insert_user, update_user, delete_user, create_table, get_new_id +import extra_streamlit_components as stx + +########################################### +# 화면 함수들 # +########################################### + +def init_router(): + return stx.Router({"/": home, "/home": home, "/add": add_new_user, "/update": update_userinfo, "/delete": delete_userinfo, "/search": search_userinfo}) + +def selection_changed(df): + st.write(df) + +def show_header(): + st.title("사용자 관리 시스템") + +def home(): + show_header() + col1, col2, col3, col4 = st.columns(4) + if col3.button("사용자 추가", use_container_width=True): + router.route("/add") + if col4.button("사용자 검색", use_container_width=True): + router.route("/search") + users = fetch_users() + df = pd.DataFrame(users, columns=['id', 'name', 'email', 'age', 'created_at']) + st.dataframe(df, use_container_width=True, hide_index=True) + + # df에서 df["id"] + df["name"] => df["id_name"] 컬럼을 추가 + df["id_name"] = df["id"].astype(str) + ":" + df["name"] + selected = st.selectbox("사용자 선택", df["id_name"]) + # st.write("선택된 id_name:", selected) + st.session_state["selected"] = selected + col1, col2 = st.columns(2) + if col1.button("사용자 수정", use_container_width=True): + router.route("/update") + if col2.button("사용자 삭제",use_container_width=True): + router.route("/delete") + +def add_new_user(): + show_header() + st.subheader("사용자 정보 입력") + with st.form(key='user_form'): + name = st.text_input(label="이름") + email = st.text_input(label="이메일") + age = st.number_input(label="나이", min_value=0, max_value=150) + submitted = st.form_submit_button(label='저장하기', use_container_width=True) + if submitted: + created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + user = {'id': get_new_id(), 'name': name, 'email': email, 'age': age, 'created_at': created_at} + insert_user(user) + st.success("저장되었습니다.") + if st.button("사용자 목록", use_container_width=True): + router.route("/home") + +def update_userinfo(): + show_header() + st.subheader("사용자 정보 수정") + selected = st.session_state["selected"] # "1:전상현" + user = fetch_user_by_id(int(selected.split(":")[0])) + with st.form(key='user_form'): + id = st.text_input(label="ID", value=user["id"], disabled=True) + name = st.text_input(label="이름", value=user["name"]) + email = st.text_input(label="이메일", value=user["email"]) + age = st.number_input(label="나이", min_value=0, max_value=150, value=user["age"]) + created_at = user["created_at"] + submitted = st.form_submit_button(label='저장하기', use_container_width=True) + if submitted: + user = {'id': int(id), 'name': name, 'email': email, 'age': age, 'created_at': created_at} + update_user(user) + st.success("저장되었습니다.") + if st.button("사용자 목록", use_container_width=True): + router.route("/home") + + +def delete_userinfo(): + show_header() + st.subheader("사용자 정보 삭제") + selected = st.session_state["selected"] # "1:전상현" + user = fetch_user_by_id(int(selected.split(":")[0])) + with st.form(key='user_form'): + id = st.text_input(label="ID", value=user["id"], disabled=True) + name = st.text_input(label="이름", value=user["name"]) + email = st.text_input(label="이메일", value=user["email"]) + age = st.number_input(label="나이", min_value=0, max_value=150, value=user["age"]) + created_at = user["created_at"] + submitted = st.form_submit_button(label='삭제하기', use_container_width=True) + if submitted: + delete_user(int(id)) + st.success("삭제되었습니다.") + router.route("/home") + if st.button("사용자 목록", use_container_width=True): + router.route("/home") + +def search_userinfo(): + show_header() + st.subheader("사용자 정보 검색") + # users = fetch_users() + # df = pd.DataFrame(users, columns=['id', 'name', 'email', 'age', 'created_at']) + with st.form(key='user_form'): + search_type = st.selectbox(label="검색 유형", options=["이름", "전자메일"]) + search = st.text_input(label="검색어") + submitted = st.form_submit_button(label='검색하기', use_container_width=True) + if submitted: + users = fetch_users() + df = pd.DataFrame(users, columns=['id', 'name', 'email', 'age', 'created_at']) + if search_type == "이름": + df = df[df["name"].str.contains(search, case=False)] + st.dataframe(df, use_container_width=True, hide_index=True) + elif search_type == "전자메일": + df = df[df["email"].str.contains(search, case=False)] + st.dataframe(df, use_container_width=True, hide_index=True) + + # df에서 df["id"] + df["name"] => df["id_name"] 컬럼을 추가 + df["id_name"] = df["id"].astype(str) + ":" + df["name"] + selected = st.selectbox("사용자 선택", df["id_name"]) + # st.write("선택된 id_name:", selected) + st.session_state["selected"] = selected + col1, col2 = st.columns(2) + if col1.button("사용자 수정", use_container_width=True): + router.route("/update") + if col2.button("사용자 삭제",use_container_width=True): + router.route("/delete") + + if st.button("사용자 목록", use_container_width=True): + router.route("/home") + +# main 함수 +create_table() +if "selected" not in st.session_state: + st.session_state["selected"] = None + +router = init_router() +router.show_route_view() + diff --git a/youtubedb.json b/youtubedb.json new file mode 100644 index 0000000..d1ea872 --- /dev/null +++ b/youtubedb.json @@ -0,0 +1 @@ +{"_default": {"1": {"category": "\uc0c8\uc18c\uc2dd", "title": "8\uc6d4 1\uc77c \ubd80\ud130..\uc6b0\uc774\uc2e0\uc124\uc120 \ud0c8 \ub54c \uad50\ud1b5\uce74\ub4dc \uc548 \ucc0d\uc5b4\ub3c4 \uc790\ub3d9\uacb0\uc81c", "date": "2023-07-01", "url": "https://youtu.be/Qvhukbs2pmc"}, "2": {"category": "\ud56b\ud074\ub9bd", "title": "\uc6b0\uc8fc \uccb4\ub958 \uae30\uac04\uc774 \uae38\uc5b4\uc9c0\uba70 \uc2dc\uc791\ub41c \ub204\uc6cc \uc9c0\ub0b4\uae30 \uc2e4\ud5d8", "date": "2023-07-10", "url": "https://youtu.be/URhpRjJgWlc"}, "3": {"category": "\uc778\ud130\ubdf0", "title": "\uce74\uc774\uc2a4\ud2b8\uc640 \uacf5\ub3d9\uac1c\ubc1c\ud55c '\ub2e4\ud06c \uc6f9' \uc804\uc6a9 AI '\ub2e4\ud06c\ubc84\ud2b8'", "date": "2023-06-01", "url": "https://youtu.be/6LMKuWYQCYs"}, "4": {"category": "\uc0ac\uc774\uc5b8\uc2a4", "title": "\uc6b0\uc8fc\uccad, \uc0ac\uc2e4\uc0c1 \uc5f0\ub0b4 \uac1c\uccad \ubd88\uac00..\ud56d\uc6b0\uc5f0 \ub0b4\ubd80\uc5d0\uc11c\ub3c4 \uc124\ub9bd \uc758\uacac \uc5c7\uac08\ub824", "date": "2023-05-01", "url": "https://youtu.be/7mIHv7bayMw"}}} \ No newline at end of file