This commit is contained in:
Envy_PC 2025-03-28 12:27:30 +09:00
parent 1dc97cafd7
commit 4490580484
17 changed files with 327 additions and 273 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
.venv/
module/__pycache__

32
app.log
View File

@ -0,0 +1,32 @@
[2025-03-27 17:28:05,955] [DEBUG] Initializing DBManager...
[2025-03-27 17:28:06,333] [DEBUG] DBManager initialized
[2025-03-27 17:28:06,334] [DEBUG] Initializing LoginDialog...
[2025-03-27 17:28:06,335] [DEBUG] Entering LoginDialog.create_dialog()
[2025-03-27 17:28:06,336] [DEBUG] Exiting LoginDialog.create_dialog()
[2025-03-27 17:28:06,337] [DEBUG] LoginDialog.show() 호출됨
[2025-03-27 17:30:46,802] [DEBUG] Initializing DBManager...
[2025-03-27 17:30:47,172] [DEBUG] DBManager initialized
[2025-03-27 17:30:47,173] [DEBUG] Initializing LoginDialog...
[2025-03-27 17:30:47,174] [DEBUG] Entering LoginDialog.create_dialog()
[2025-03-27 17:30:47,175] [DEBUG] Exiting LoginDialog.create_dialog()
[2025-03-27 17:30:47,176] [DEBUG] LoginDialog.show() 호출됨
[2025-03-27 17:31:45,862] [DEBUG] Initializing DBManager...
[2025-03-27 17:31:46,229] [DEBUG] DBManager initialized
[2025-03-27 17:31:46,230] [DEBUG] Initializing LoginDialog...
[2025-03-27 17:31:46,230] [DEBUG] Entering LoginDialog.create_dialog()
[2025-03-27 17:31:46,232] [DEBUG] Exiting LoginDialog.create_dialog()
[2025-03-27 17:31:46,232] [DEBUG] LoginDialog.show() 호출됨
[2025-03-27 17:36:55,275] [DEBUG] Initializing DBManager...
[2025-03-27 17:36:55,656] [DEBUG] DBManager initialized
[2025-03-27 17:37:12,570] [DEBUG] Initializing DBManager...
[2025-03-27 17:37:12,930] [DEBUG] DBManager initialized
[2025-03-27 17:37:12,930] [DEBUG] Initializing LoginDialog...
[2025-03-27 17:37:12,932] [DEBUG] Entering LoginDialog.create_dialog()
[2025-03-27 17:37:12,933] [DEBUG] Exiting LoginDialog.create_dialog()
[2025-03-27 17:37:12,933] [DEBUG] LoginDialog.show() 호출됨
[2025-03-27 17:38:18,459] [DEBUG] Initializing DBManager...
[2025-03-27 17:38:18,818] [DEBUG] DBManager initialized
[2025-03-27 17:38:18,818] [DEBUG] Initializing LoginDialog...
[2025-03-27 17:38:18,819] [DEBUG] Entering LoginDialog.create_dialog()
[2025-03-27 17:38:18,820] [DEBUG] Exiting LoginDialog.create_dialog()
[2025-03-27 17:38:18,821] [DEBUG] LoginDialog.show() 호출됨

209
main.py
View File

@ -1,203 +1,42 @@
import flet as ft
from modules import logger, login, backend, product_filter, export
from modules import logger, login
from modules.setting_manager import SettingsManager
from modules.db_manager import DBeManager
import logging
# 전역 변수 (데모용 데이터 저장)
market_list = []
sold_products = []
filtered_products = []
sourced_products = []
from modules.db_manager import DBManager
from modules.main_window import MainWindow
def main(page: ft.Page):
page.title = "Modern Market and Product Manager"
page.window_width = 1000
page.window_height = 700
# 하단 로그 출력용 텍스트 위젯
log_display = ft.Text(value="", size=12)
# 로거 콜백 예시 (단순 콘솔 출력)
def gui_log_callback(formatted_message: str):
log_display.value += formatted_message + "\n"
page.update()
print(formatted_message)
# 로거, 설정 관리자, SupabaseManager 초기화
# 로거, 설정 관리자, DBManager 초기화
app_logger = logger.get_logger(gui_callback=gui_log_callback)
settings_manager = SettingsManager()
supabase_manager = DBeManager(app_logger)
db_manager = DBManager(app_logger)
# 로그인 다이얼로그 실행 (비동기)
async def do_login():
login_dialog = login.LoginDialog(page, app_logger, settings_manager, supabase_manager)
logged_in = await login_dialog.show()
if logged_in:
# 로그인 성공 시 호출될 콜백 함수
def on_login_success():
# 로그인 다이얼로그가 닫힌 뒤 호출됨
page.controls.clear()
page.add(ft.Text("로그인 성공! 메인 화면입니다."), log_display)
else:
page.add(ft.Text("로그인 실패!"), log_display)
page.async_run(do_login)
main_win = MainWindow(page)
for ctrl in main_win.controls:
page.add(ctrl)
page.update()
# 마켓 탭 UI 구성
market_tab_content = ft.Column([
ft.Row([
ft.ElevatedButton("마켓목록 가져오기", on_click=lambda e: load_market_list(page)),
ft.ElevatedButton("팔린상품 가져오기", on_click=lambda e: load_sold_products(page)),
ft.ElevatedButton("마켓추가하기", on_click=lambda e: add_market(page))
]),
ft.DataTable(
columns=[
ft.DataColumn(ft.Text("마켓이름")),
ft.DataColumn(ft.Text("마켓 URL")),
ft.DataColumn(ft.Text("메모"))
],
rows=[],
expand=True,
key="market_table"
)
], scroll=ft.ScrollMode.AUTO)
# 상품 탭 UI 구성
product_tab_content = ft.Column([
ft.Row([
ft.ElevatedButton("금지어필터링", on_click=lambda e: filter_forbidden(page)),
ft.ElevatedButton("카테고리 필터링", on_click=lambda e: filter_category(page)),
ft.Dropdown(
label="소싱몰 목록",
options=[
ft.dropdown.Option("타오바오"),
ft.dropdown.Option("1688")
],
key="sourcing_market"
),
ft.ElevatedButton("소싱하기", on_click=lambda e: sourcing_products(page)),
ft.ElevatedButton("출력", on_click=lambda e: export_products(page))
]),
ft.DataTable(
columns=[
ft.DataColumn(ft.Text("상품명")),
ft.DataColumn(ft.Text("카테고리")),
ft.DataColumn(ft.Text("이미지 URL")),
ft.DataColumn(ft.Text("소싱 URL"))
],
rows=[],
expand=True,
key="product_table"
)
], scroll=ft.ScrollMode.AUTO)
# 금지어 관리 탭 (추후 구현)
forbidden_tab_content = ft.Column([
ft.Text("금지어 관리 탭 내용 (추후 구현)")
])
# 카테고리 관리 탭 (추후 구현)
category_tab_content = ft.Column([
ft.Text("카테고리 관리 탭 내용 (추후 구현)")
])
# 메인 탭 생성
tabs = ft.Tabs(
selected_index=0,
tabs=[
ft.Tab(text="마켓", content=market_tab_content),
ft.Tab(text="상품", content=product_tab_content),
ft.Tab(text="금지어 관리", content=forbidden_tab_content),
ft.Tab(text="카테고리 관리", content=category_tab_content)
],
key="main_tabs"
# 로그인 다이얼로그 생성
login_dialog = login.LoginDialog(
page=page,
logger=app_logger,
settings_manager=settings_manager,
db_manager=db_manager,
on_login_success=on_login_success
)
# 페이지 레이아웃 구성
page.add(tabs, log_display)
# 로그인 다이얼로그 표시
login_dialog.show()
# 로그 추가 함수 (각 모듈에서 호출 가능하도록 page.session에 저장)
def append_log(message: str):
current = log_display.value
log_display.value = current + message + "\n"
page.update()
page.session.set("append_log", append_log)
def load_market_list(page: ft.Page):
global market_list
page.session.get("append_log")("Fetching market list...")
market_list = backend.get_market_list()
market_rows = []
for m in market_list:
row = ft.DataRow(cells=[
ft.DataCell(ft.Text(m.get("name", ""))),
ft.DataCell(ft.Text(m.get("url", ""))),
ft.DataCell(ft.Text(m.get("memo", "")))
])
market_rows.append(row)
market_table: ft.DataTable = page.get_control("market_table")
market_table.rows = market_rows
page.session.get("append_log")("Market list loaded.")
page.update()
def load_sold_products(page: ft.Page):
global sold_products, filtered_products, sourced_products
page.session.get("append_log")("Fetching sold products for each market...")
sold_products = backend.get_sold_products(market_list)
filtered_products = sold_products.copy()
page.session.get("append_log")("Sold products loaded. Switching to 상품 탭.")
update_product_table(page, filtered_products)
tabs: ft.Tabs = page.get_control("main_tabs")
tabs.selected_index = 1
page.update()
def update_product_table(page: ft.Page, products):
product_rows = []
for p in products:
row = ft.DataRow(cells=[
ft.DataCell(ft.Text(p.get("name", ""))),
ft.DataCell(ft.Text(p.get("category", ""))),
ft.DataCell(ft.Text(p.get("image_url", ""))),
ft.DataCell(ft.Text(p.get("sourcing_url", "")))
])
product_rows.append(row)
product_table: ft.DataTable = page.get_control("product_table")
product_table.rows = product_rows
page.update()
def add_market(page: ft.Page):
page.session.get("append_log")("Add market functionality not implemented yet.")
page.update()
def filter_forbidden(page: ft.Page):
global filtered_products
page.session.get("append_log")("Filtering products with forbidden words...")
filtered_products = product_filter.filter_forbidden_words(filtered_products)
update_product_table(page, filtered_products)
page.session.get("append_log")("Forbidden words filtering applied.")
page.update()
def filter_category(page: ft.Page):
global filtered_products
page.session.get("append_log")("Filtering products with forbidden categories...")
filtered_products = product_filter.filter_forbidden_categories(filtered_products)
update_product_table(page, filtered_products)
page.session.get("append_log")("Category filtering applied.")
page.update()
def sourcing_products(page: ft.Page):
global sourced_products, filtered_products
sourcing_market: ft.Dropdown = page.get_control("sourcing_market")
selected_market = sourcing_market.value
page.session.get("append_log")(f"Starting sourcing using {selected_market}...")
sourced_products = []
for product in filtered_products:
sourcing_url = backend.sourcing_product(product.get("image_url", ""), selected_market)
product["sourcing_url"] = sourcing_url
sourced_products.append(product)
update_product_table(page, sourced_products)
page.session.get("append_log")("Sourcing completed.")
page.update()
def export_products(page: ft.Page):
page.session.get("append_log")("Exporting products to Excel...")
export.export_to_excel(sourced_products)
page.session.get("append_log")("Products exported and folder opened.")
page.update()
if __name__ == "__main__":
ft.app(target=main)
ft.app(target=main)

Binary file not shown.

View File

@ -1,40 +1,42 @@
import time
import random
import requests
from modules import logger
app_logger = logger.get_logger()
import logging
def get_market_list():
# Supabase에서 마켓 목록을 가져오는 기능 (시뮬레이션)
app_logger.info("Fetching market list from supabase...")
# 데모용 더미 데이터
logger = logging.getLogger("FletLogger")
logger.debug("Entering get_market_list()")
time.sleep(1)
return [
data = [
{"name": "Market A", "url": "https://market-a.com", "memo": "Memo A"},
{"name": "Market B", "url": "https://market-b.com", "memo": "Memo B"},
]
logger.debug(f"Market list retrieved: {data}")
logger.debug("Exiting get_market_list()")
return data
def get_sold_products(markets):
# 각 마켓의 판매 상품 목록을 가져오는 기능 (시뮬레이션)
app_logger.info("Fetching sold products from markets...")
logger = logging.getLogger("FletLogger")
logger.debug("Entering get_sold_products()")
products = []
for market in markets:
time.sleep(0.5) # 네트워크 딜레이 시뮬레이션
# 더미 상품 데이터
time.sleep(0.5)
for i in range(3):
products.append({
prod = {
"name": f"Product {i} from {market['name']}",
"category": random.choice(["Electronics", "Clothing", "Forbidden Category"]),
"image_url": "https://via.placeholder.com/150",
"sourcing_url": ""
})
}
products.append(prod)
logger.debug(f"Sold products retrieved: {products}")
logger.debug("Exiting get_sold_products()")
return products
def sourcing_product(image_url, sourcing_market):
# RapidAPI를 이용한 소싱 기능 (시뮬레이션)
app_logger.info(f"Sourcing product for image {image_url} from {sourcing_market}...")
# 실제 구현 시 requests를 통해 API 호출
logger = logging.getLogger("FletLogger")
logger.debug("Entering sourcing_product()")
time.sleep(0.3)
# 더미 소싱 URL 반환
return f"https://sourcing.example.com/{sourcing_market}/" + image_url.split("/")[-1]
url = f"https://sourcing.example.com/{sourcing_market}/" + image_url.split("/")[-1]
logger.debug(f"Sourcing URL: {url}")
logger.debug("Exiting sourcing_product()")
return url

View File

@ -1,36 +1,38 @@
from supabase import create_client, Client
from supabase.lib.client_options import ClientOptions
from datetime import datetime, timezone
import logging
import traceback
class DBeManager:
class DBManager:
"""
SupabaseManager는 Supabase 클라이언트를 래핑하여 로그인, 사용자 정보 조회,
DBManager는 Supabase 클라이언트를 래핑하여 로그인, 사용자 정보 조회,
마지막 로그인 시간 업데이트 여러 API 호출을 수행합니다.
"""
def __init__(self, logger: logging.Logger):
self.logger = logger
self.logger.log("Initializing DBManager...", level=logging.DEBUG)
# 실제 Supabase URL과 KEY로 수정하세요.
self.url: str = "http://146.56.101.199:8000"
self.key: str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"
self.client: Client = create_client(self.url, self.key)
self.access_token = None
self.refresh_token = None
self.logger.log("DBManager initialized", level=logging.DEBUG)
def update_client_with_token(self, access_token: str):
self.logger.log("Entering update_client_with_token()", level=logging.DEBUG)
try:
options = ClientOptions(headers={"Authorization": f"Bearer {access_token}"})
self.client = create_client(self.url, self.key, options=options)
self.logger.log("Client updated with JWT token", level=logging.DEBUG)
except Exception as ex:
self.logger.log(f"update_client_with_token error: {ex}", level=logging.ERROR, exc_info=True)
self.logger.log("Exiting update_client_with_token()", level=logging.DEBUG)
def login(self, email: str, password: str) -> dict:
"""
Supabase Auth를 사용해 로그인합니다.
로그인 성공 사용자 정보를 담은 dict를, 실패 error 키를 포함한 dict를 반환합니다.
"""
self.logger.log("Entering DBManager.login()", level=logging.DEBUG)
try:
response = self.client.auth.sign_in_with_password({"email": email, "password": password})
if response.session:
@ -46,6 +48,7 @@ class DBeManager:
"nickname": response.user.user_metadata.get("nickname", "Unknown")
}
self.logger.log(f"로그인 성공: {user_info}", level=logging.DEBUG)
self.logger.log("Exiting DBManager.login()", level=logging.DEBUG)
return user_info
else:
self.logger.log("로그인 실패: 세션 없음", level=logging.WARNING)
@ -55,12 +58,11 @@ class DBeManager:
return {"error": str(e)}
def get_auth_user_info(self, user_id: str) -> dict:
"""
auth.users 테이블(인증 스키마)에서 사용자 정보를 조회합니다.
"""
self.logger.log(f"Entering get_auth_user_info(user_id={user_id})", level=logging.DEBUG)
try:
response = self.client.from_("users").select("email_confirmed_at").eq("id", user_id).execute()
if response.data and len(response.data) > 0:
self.logger.log(f"Retrieved auth user info: {response.data[0]}", level=logging.DEBUG)
return response.data[0]
else:
self.logger.log("auth.users에서 사용자 정보를 찾지 못했습니다.", level=logging.WARNING)
@ -70,10 +72,7 @@ class DBeManager:
return {}
def get_full_user_info(self, user_id: str) -> dict:
"""
public.users 테이블과 membership_levels 테이블을 조합하여
전체 사용자 정보를 반환합니다.
"""
self.logger.log(f"Entering get_full_user_info(user_id={user_id})", level=logging.DEBUG)
try:
user_resp = self.client.table("users").select("*").eq("id", user_id).execute()
if not user_resp.data:
@ -84,18 +83,18 @@ class DBeManager:
membership_resp = self.client.table("membership_levels").select("*").eq("level", membership_level).execute()
membership_info = membership_resp.data[0] if membership_resp.data else {}
full_info = {**user_info, "membership_level_data": membership_info}
self.logger.log(f"Full user info retrieved: {full_info}", level=logging.DEBUG)
return full_info
except Exception as e:
self.logger.log(f"get_full_user_info 에러: {e}", level=logging.ERROR, exc_info=True)
return {}
def update_last_login(self, user_id: str):
"""
사용자의 마지막 로그인 시간을 현재 시간(UTC)으로 업데이트합니다.
"""
self.logger.log(f"Entering update_last_login(user_id={user_id})", level=logging.DEBUG)
try:
now_iso = datetime.now(timezone.utc).isoformat()
self.client.table("users").update({"last_login": now_iso}).eq("id", user_id).execute()
self.logger.log(f"Last login updated for user {user_id}", level=logging.INFO)
except Exception as e:
self.logger.log(f"update_last_login 에러: {e}", level=logging.ERROR, exc_info=True)
self.logger.log("Exiting update_last_login()", level=logging.DEBUG)

View File

@ -2,11 +2,14 @@ import pandas as pd
import os
from datetime import datetime
import webbrowser
import logging
def export_to_excel(products):
logger = logging.getLogger("FletLogger")
logger.debug("Entering export_to_excel()")
if not products:
logger.debug("No products to export.")
return
# 상품을 50개씩 분할
batches = [products[i:i+50] for i in range(0, len(products), 50)]
export_folder = "exported_products"
os.makedirs(export_folder, exist_ok=True)
@ -14,5 +17,6 @@ def export_to_excel(products):
df = pd.DataFrame(batch)
filename = os.path.join(export_folder, f"products_batch_{idx+1}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")
df.to_excel(filename, index=False)
# 저장 폴더 열기
logger.debug(f"Exported batch {idx+1} to {filename}")
webbrowser.open(os.path.abspath(export_folder))
logger.debug("Exiting export_to_excel()")

View File

@ -1,30 +1,36 @@
import flet as ft
from flet import (
AlertDialog, TextField, Checkbox, ElevatedButton, Text, Column, Row,
MainAxisAlignment
)
import asyncio
from flet import AlertDialog, TextField, Checkbox, ElevatedButton, Text, Column, Row, MainAxisAlignment
import logging
class LoginDialog:
"""
flet 기반 로그인 다이얼로그.
- 이메일, 비밀번호 입력란
- "정보 저장" 체크박스 (체크 , SettingsManager를 통해 사용자 정보를 저장)
- "비밀번호 보기" 체크박스 (비밀번호 입력란 에코 모드 전환)
- 로그인 비밀번호 찾기 버튼
- 로그인 성공 on_login_success 콜백 호출
- 로그인 실패 메시지 표시
- 비밀번호 보기 토글, 정보 저장 체크박스
"""
def __init__(
self,
page: ft.Page,
logger: logging.Logger,
settings_manager,
db_manager,
on_login_success
):
"""
on_login_success: 로그인 성공 호출할 콜백 함수 (메인 윈도우로 전환 )
"""
def __init__(self, page: ft.Page, logger: logging.Logger, settings_manager, supabase_manager):
self.page = page
self.logger = logger
self.settings_manager = settings_manager
self.supabase_manager = supabase_manager
self.result = None # 로그인 성공 여부 (True/False)
self.db_manager = db_manager
self.on_login_success = on_login_success
self.result = None # True/False
self.logger.log("Initializing LoginDialog...", level=logging.DEBUG)
self.create_dialog()
def create_dialog(self):
# 입력 필드 및 체크박스
self.logger.log("Entering LoginDialog.create_dialog()", level=logging.DEBUG)
self.email_field = TextField(label="이메일", width=300)
self.password_field = TextField(label="비밀번호", width=300, password=True)
self.remember_checkbox = Checkbox(label="정보 저장")
@ -33,64 +39,69 @@ class LoginDialog:
self.show_password_checkbox.on_change = self.toggle_password
# 버튼들
self.login_button = ElevatedButton("로그인", on_click=self.on_login)
self.reset_button = ElevatedButton("비밀번호 찾기", on_click=self.on_reset)
content = Column([
# AlertDialog 구성
self.dialog = AlertDialog(
title=Text("로그인"),
content=Column([
self.email_field,
self.password_field,
Row([self.remember_checkbox, self.show_password_checkbox]),
self.error_text,
Row([self.login_button, self.reset_button], alignment=MainAxisAlignment.CENTER)
])
self.dialog = AlertDialog(
title=Text("로그인"),
content=content,
]),
actions_alignment=MainAxisAlignment.CENTER
)
self.logger.log("Exiting LoginDialog.create_dialog()", level=logging.DEBUG)
def show(self):
"""
로그인 다이얼로그를 표시한다. (비동기 루프 없이, 콜백 방식)
"""
self.logger.log("LoginDialog.show() 호출됨", level=logging.DEBUG)
self.page.dialog = self.dialog
self.dialog.open = True
self.page.update()
def toggle_password(self, e: ft.ControlEvent):
self.logger.log("toggle_password() 호출됨", level=logging.DEBUG)
self.password_field.password = not self.show_password_checkbox.value
self.page.update()
def on_login(self, e: ft.ControlEvent):
self.logger.log("on_login() 호출됨", level=logging.DEBUG)
email = self.email_field.value.strip()
password = self.password_field.value.strip()
if not email or not password:
self.error_text.value = "이메일과 비밀번호를 모두 입력하세요."
self.logger.log("on_login(): 입력값 부족", level=logging.WARNING)
self.page.update()
return
# 실제 Supabase 로그인을 시도합니다.
result = self.supabase_manager.login(email, password)
result = self.db_manager.login(email, password)
if "error" not in result:
self.logger.log(f"로그인 성공: {result}", level=logging.DEBUG)
# 마지막 로그인 시간 업데이트
self.supabase_manager.update_last_login(result["id"])
self.db_manager.update_last_login(result["id"])
if self.remember_checkbox.value:
user_info = {"email": email, "password": password, "id": result["id"]}
self.settings_manager.save_user_info(user_info)
self.result = True
# 로그인 성공 => 다이얼로그 닫고 콜백 호출
self.dialog.open = False
self.page.dialog = None
self.page.update()
# 메인윈도우로 전환
if self.on_login_success:
self.on_login_success()
else:
self.error_text.value = f"로그인 실패: {result['error']}"
self.logger.log(f"로그인 실패: {result['error']}", level=logging.WARNING)
self.page.update()
def on_reset(self, e: ft.ControlEvent):
self.logger.log("on_reset() 호출됨", level=logging.DEBUG)
self.error_text.value = "비밀번호 찾기 기능은 구현되지 않았습니다."
self.page.update()
async def show(self) -> bool:
"""
로그인 다이얼로그를 보여주고, 로그인 성공 True를 반환합니다.
(비동기적으로 result가 채워질 때까지 기다립니다.)
"""
while self.result is None:
await asyncio.sleep(0.1)
return self.result

159
modules/main_window.py Normal file
View File

@ -0,0 +1,159 @@
import flet as ft
import logging
from modules import backend, product_filter, export
class MainWindow:
def __init__(self, page: ft.Page):
self.page = page
self.logger = logging.getLogger("FletLogger")
self.logger.debug("MainWindow initialized")
self.market_list = []
self.sold_products = []
self.filtered_products = []
self.sourced_products = []
self.controls = self.build_layout()
def build_layout(self):
self.logger.debug("Building main window layout")
# 마켓 탭
self.market_table = ft.DataTable(
columns=[
ft.DataColumn(ft.Text("마켓이름")),
ft.DataColumn(ft.Text("마켓 URL")),
ft.DataColumn(ft.Text("메모"))
],
rows=[],
expand=True,
key="market_table"
)
market_tab_content = ft.Column([
ft.Row([
ft.ElevatedButton("마켓목록 가져오기", on_click=self.load_market_list),
ft.ElevatedButton("팔린상품 가져오기", on_click=self.load_sold_products),
ft.ElevatedButton("마켓추가하기", on_click=self.add_market)
]),
self.market_table
], scroll=ft.ScrollMode.AUTO)
# 상품 탭
self.product_table = ft.DataTable(
columns=[
ft.DataColumn(ft.Text("상품명")),
ft.DataColumn(ft.Text("카테고리")),
ft.DataColumn(ft.Text("이미지 URL")),
ft.DataColumn(ft.Text("소싱 URL"))
],
rows=[],
expand=True,
key="product_table"
)
self.sourcing_market_dropdown = ft.Dropdown(
label="소싱몰 목록",
options=[
ft.dropdown.Option("타오바오"),
ft.dropdown.Option("1688")
],
key="sourcing_market"
)
product_tab_content = ft.Column([
ft.Row([
ft.ElevatedButton("금지어필터링", on_click=self.filter_forbidden),
ft.ElevatedButton("카테고리 필터링", on_click=self.filter_category),
self.sourcing_market_dropdown,
ft.ElevatedButton("소싱하기", on_click=self.sourcing_products),
ft.ElevatedButton("출력", on_click=self.export_products)
]),
self.product_table
], scroll=ft.ScrollMode.AUTO)
# 기타 탭
forbidden_tab_content = ft.Column([ft.Text("금지어 관리 탭 (추후 구현)")])
category_tab_content = ft.Column([ft.Text("카테고리 관리 탭 (추후 구현)")])
self.tabs = ft.Tabs(
selected_index=0,
tabs=[
ft.Tab(text="마켓", content=market_tab_content),
ft.Tab(text="상품", content=product_tab_content),
ft.Tab(text="금지어 관리", content=forbidden_tab_content),
ft.Tab(text="카테고리 관리", content=category_tab_content)
],
key="main_tabs"
)
# 로그 출력
self.log_display = ft.Text(value="", size=12)
def append_log(message: str):
self.log_display.value += message + "\n"
self.page.update()
self.page.session.set("append_log", append_log)
layout = [self.tabs, self.log_display]
self.logger.debug("Main window layout built")
return layout
# 이하 메서드들은 모두 디버그 로깅 + 기능 수행
def load_market_list(self, e):
self.logger.debug("load_market_list() 호출됨")
self.page.session.get("append_log")("Fetching market list...")
self.market_list = backend.get_market_list()
rows = []
for m in self.market_list:
row = ft.DataRow(cells=[
ft.DataCell(ft.Text(m.get("name", ""))),
ft.DataCell(ft.Text(m.get("url", ""))),
ft.DataCell(ft.Text(m.get("memo", "")))
])
rows.append(row)
self.market_table.rows = rows
self.page.session.get("append_log")("Market list loaded.")
self.page.update()
def load_sold_products(self, e):
self.logger.debug("load_sold_products() 호출됨")
self.page.session.get("append_log")("Fetching sold products for each market...")
self.sold_products = backend.get_sold_products(self.market_list)
self.filtered_products = self.sold_products.copy()
self.page.session.get("append_log")("Sold products loaded. Switching to 상품 탭.")
self.update_product_table(self.filtered_products)
self.tabs.selected_index = 1
self.page.update()
def update_product_table(self, products):
self.logger.debug("update_product_table() 호출됨")
rows = []
for p in products:
row = ft.DataRow(cells=[
ft.DataCell(ft.Text(p.get("name", ""))),
ft.DataCell(ft.Text(p.get("category", ""))),
ft.DataCell(ft.Text(p.get("image_url", ""))),
ft.DataCell(ft.Text(p.get("sourcing_url", "")))
])
rows.append(row)
self.product_table.rows = rows
self.page.update()
def add_market(self, e):
self.logger.debug("add_market() 호출됨")
self.page.session.get("append_log")("Add market functionality not implemented yet.")
self.page.update()
def filter_forbidden(self, e):
self.logger.debug("filter_forbidden() 호출됨")
self.page.session.get("append_log")("Filtering products with forbidden words...")
self.filtered_products = product_filter.filter_forbidden_words(self.filtered_products)
self.update_product_table(self.filtered_products)
self.page.session.get("append_log")("Forbidden words filtering applied.")
self.page.update()
def filter_category(self, e):
self.logger.debug("filter_category() 호출됨")
self.page.session.get("append_log")("Filtering products with forbidden categories...")
self.filtered_products = product_filter.filter_forbidden_categories(self.filtered_products)
self.update_product_table(self.filtered_products)
self.page.session.get("append_log")("Category filtering applied.")
self.page.update()
def sourcing_products(self, e):
self.logger.debug("sourcing_products() 호출됨")
self

View File

@ -1,18 +1,25 @@
def filter_forbidden_words(products):
# 데모용 금지어 리스트
import logging
logger = logging.getLogger("FletLogger")
logger.debug("Entering filter_forbidden_words()")
forbidden_words = ["bad", "illegal", "금지어"]
filtered = []
for product in products:
# 상품명이 금지어를 포함하지 않을 경우에만 추가
if not any(word in product.get("name", "").lower() for word in forbidden_words):
filtered.append(product)
logger.debug(f"Filtered products (forbidden words): {filtered}")
logger.debug("Exiting filter_forbidden_words()")
return filtered
def filter_forbidden_categories(products):
# 데모용 금지 카테고리 리스트
import logging
logger = logging.getLogger("FletLogger")
logger.debug("Entering filter_forbidden_categories()")
forbidden_categories = ["Forbidden Category"]
filtered = []
for product in products:
if product.get("category", "") not in forbidden_categories:
filtered.append(product)
logger.debug(f"Filtered products (forbidden categories): {filtered}")
logger.debug("Exiting filter_forbidden_categories()")
return filtered

View File

@ -3,7 +3,7 @@ import os
class SettingsManager:
"""
로컬 JSON 파일("settings.json") 이용해 사용자 설정(, 로그인 정보, GUI 설정 ) 저장/불러옵니다.
로컬 JSON 파일("settings.json") 이용해 사용자 설정(로그인 정보, GUI 설정 ) 저장/불러옵니다.
"""
def __init__(self, filename="settings.json"):
self.filename = filename
@ -11,15 +11,15 @@ class SettingsManager:
self.load_settings()
def load_settings(self):
if os.path.exists(self.filename):
try:
if os.path.exists(self.filename):
with open(self.filename, "r", encoding="utf-8") as f:
self.settings = json.load(f)
else:
self.settings = {}
except Exception as e:
print("설정 불러오기 오류:", e)
self.settings = {}
else:
self.settings = {}
def save_settings(self):
try: