TodoList Web 应用
项目简介
这是一个基于 Dash 和 SQLAlchemy 的现代化 TodoList Web 应用,提供了简朴而强大的待办事项管理功能。
主要特性
- 添加新的待办事项
- 删除待办事项
- 标记待办事项为已完成/未完成
- 分页展示待办事项列表
- 实时更新和交互
技术栈
- Python
- Dash (Web框架)
- SQLAlchemy (ORM)
- SQLite (数据库)
- Dash Bootstrap Components (UI组件)
功能详细分析
待办事项管理
应用提供了一个统一的回调函数 manage_todos(),处理以下交互逻辑:
- 删除待办事项
- 添加新的待办事项
- 切换待办事项状态
- 分页展示待办事项列表
删除功能
- 支持通过删除按钮移除指定的待办事项
- 添加了详细的错误处理和日志记录
- 确保只处理有效的点击变乱
状态切换
- 支持多种状态切换方式(复选框和开关)
- 灵活处理差别范例的状态值
- 实时更新待办事项完成状态
分页
- 默认每页显示10个待办事项
- 动态盘算总页数
- 支持页面间切换
调试与日志
应用内置详细的日志记录机制,记录:
- 回调上下文详情
- 触发的输入变乱
- 操纵执行情况
- 错误信息
运行项目
依赖安装
- pip install -r requirements.txt
复制代码 启动应用
访问 http://127.0.0.1:8050/ 利用应用
requirements.txt
‘’’
dash2.14.1
dash-bootstrap-components1.5.0
sqlalchemy2.0.25
plotly5.19.0
‘’’
- import dash
- import dash_bootstrap_components as dbc
- from dash import html, dcc, Input, Output, State, dash_table
- from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime
- from sqlalchemy.orm import sessionmaker, declarative_base
- from sqlalchemy.sql import func
- from datetime import datetime
- import logging
- import json # 确保导入 json 模块
- # 配置日志
- logging.basicConfig(level=logging.DEBUG,
- format='%(asctime)s - %(levelname)s: %(message)s')
- # 数据库配置
- DATABASE_URL = 'sqlite:///todolist.db'
- engine = create_engine(DATABASE_URL)
- SessionLocal = sessionmaker(bind=engine)
- Base = declarative_base()
- # 待办事项模型
- class Todo(Base):
- __tablename__ = 'todos'
-
- id = Column(Integer, primary_key=True, index=True)
- title = Column(String, index=True)
- description = Column(String, nullable=True)
- completed = Column(Boolean, default=False)
- created_at = Column(DateTime, server_default=func.now())
- updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
- # 创建数据库表
- Base.metadata.create_all(bind=engine)
- # 初始化 Dash 应用
- app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
- def get_todos(page=1, per_page=10):
- """获取分页后的待办事项"""
- db = SessionLocal()
- try:
- total_todos = db.query(Todo).count()
- offset = (page - 1) * per_page
- todos = db.query(Todo).order_by(Todo.created_at.desc()).offset(offset).limit(per_page).all()
- logging.debug(f"Total todos: {total_todos}, Page: {page}, Per page: {per_page}")
- return todos, total_todos
- finally:
- db.close()
- def add_todo(title, description):
- """添加新的待办事项"""
- db = SessionLocal()
- try:
- new_todo = Todo(title=title, description=description)
- db.add(new_todo)
- db.commit()
- db.refresh(new_todo)
- logging.debug(f"Added new todo: {new_todo.title}")
- return new_todo
- finally:
- db.close()
- def update_todo_status(todo_id, completed):
- """更新待办事项状态"""
- db = SessionLocal()
- try:
- todo = db.query(Todo).filter(Todo.id == todo_id).first()
- if todo:
- todo.completed = completed
- db.commit()
- logging.debug(f"Todo {todo_id} status updated to {completed}")
- except Exception as e:
- logging.error(f"Error updating todo status: {e}")
- finally:
- db.close()
- def delete_todo(todo_id):
- """删除待办事项"""
- db = SessionLocal()
- try:
- todo = db.query(Todo).filter(Todo.id == todo_id).first()
- if todo:
- db.delete(todo)
- db.commit()
- finally:
- db.close()
- def render_todo_list(todos):
- """渲染待办事项列表"""
- todo_list = []
- for todo in todos:
- todo_list.append(
- dbc.Card([
- dbc.CardBody([
- html.H5(todo.title, className="card-title"),
- html.P(todo.description or '', className="card-text"),
- dbc.Checklist(
- options=[{'label': '已完成', 'value': 1}],
- value=[1] if todo.completed else [],
- id={'type': 'todo-status', 'index': todo.id},
- switch=True
- ),
- dbc.Button("删除", color="danger", size="sm",
- id={'type': 'delete-todo', 'index': todo.id})
- ])
- ], className="mb-2", id=f'todo-card-{todo.id}')
- )
- return todo_list
- # 应用布局
- app.layout = dbc.Container([
- html.H1("TodoList 应用", className="text-center my-4"),
-
- # 添加待办事项表单
- dbc.Row([
- dbc.Col([
- dbc.Label("标题"),
- dbc.Input(id='todo-title', type='text', placeholder='输入待办事项标题')
- ], width=12),
- ], className="mb-3"),
-
- dbc.Row([
- dbc.Col([
- dbc.Label("描述"),
- dbc.Input(id='todo-description', type='text', placeholder='输入待办事项描述')
- ], width=12)
- ], className="mb-3"),
-
- dbc.Row([
- dbc.Col([
- dbc.Button("添加待办", id='add-todo-button', color='primary')
- ])
- ], className="mb-3"),
-
- # 待办事项列表
- html.Div(id='todo-list-container'),
-
- # 分页组件
- dbc.Pagination(
- id='todo-pagination',
- max_value=1, # 初始化为1
- fully_expanded=False
- ),
- # 调试信息显示
- html.Div(id='debug-output')
- ], fluid=True)
- # 初始化加载待办事项
- @app.callback(
- [Output('todo-list-container', 'children'),
- Output('todo-pagination', 'max_value')],
- [Input('todo-pagination', 'active_page')]
- )
- def initial_load(active_page):
- """初始加载待办事项"""
- page = active_page or 1
- per_page = 10
- todos, total_todos = get_todos(page, per_page)
-
- # 计算总页数
- total_pages = max(1, (total_todos + per_page - 1) // per_page)
-
- # 渲染待办事项列表
- todo_list = render_todo_list(todos)
-
- return [todo_list, total_pages]
- @app.callback(
- [Output('todo-list-container', 'children', allow_duplicate=True),
- Output('todo-pagination', 'max_value', allow_duplicate=True),
- Output('todo-title', 'value'),
- Output('todo-description', 'value'),
- Output('debug-output', 'children')],
- [Input('todo-pagination', 'active_page'),
- Input('add-todo-button', 'n_clicks'),
- Input({'type': 'delete-todo', 'index': dash.ALL}, 'n_clicks'),
- Input({'type': 'todo-status', 'index': dash.ALL}, 'value')],
- [State('todo-title', 'value'),
- State('todo-description', 'value'),
- State({'type': 'delete-todo', 'index': dash.ALL}, 'id'),
- State({'type': 'todo-status', 'index': dash.ALL}, 'id')],
- prevent_initial_call=True
- )
- def manage_todos(active_page, add_clicks, delete_clicks, status_values,
- title, description, delete_ids, status_ids):
- """
- 统一管理待办事项的增删改查操作
-
- 这个回调函数处理所有的交互逻辑:
- 1. 删除待办事项
- 2. 添加新的待办事项
- 3. 切换待办事项状态
- 4. 分页展示待办事项列表
-
- 参数说明:
- - active_page: 当前分页页码
- - add_clicks: 添加按钮点击次数
- - delete_clicks: 删除按钮点击次数列表
- - status_values: 状态切换值列表
- - title, description: 新建待办事项的标题和描述
- - delete_ids, status_ids: 对应的待办事项ID
- """
- ctx = dash.callback_context
-
- # 记录详细的调试日志,帮助追踪回调上下文
- logging.debug("Callback Context Details:")
- logging.debug(f"Triggered Inputs: {ctx.triggered}")
- logging.debug(f"Input Values:")
- logging.debug(f"Active Page: {active_page}")
- logging.debug(f"Add Clicks: {add_clicks}")
- logging.debug(f"Delete Clicks: {delete_clicks}")
- logging.debug(f"Delete IDs: {delete_ids}")
- logging.debug(f"Status Values: {status_values}")
- logging.debug(f"Status IDs: {status_ids}")
-
- # 初始加载:如果没有触发任何事件,默认加载第一页
- if not ctx.triggered:
- page = 1
- per_page = 10
- todos, total_todos = get_todos(page, per_page)
- total_pages = max(1, (total_todos + per_page - 1) // per_page)
- todo_list = render_todo_list(todos)
- return [todo_list, total_pages, title, description, '']
- # 初始化调试消息
- triggered = ctx.triggered
- debug_message = "Triggered Inputs:\n"
- # 处理删除待办事项
- for n_clicks, delete_id in zip(delete_clicks, delete_ids):
- logging.debug(f"Delete: n_clicks={n_clicks}, delete_id={delete_id}")
-
- # 确保是有效的点击事件(非初始 None 值)
- if n_clicks is not None and n_clicks > 0:
- todo_id = delete_id['index']
- logging.debug(f"Attempting to delete todo with ID: {todo_id}")
-
- try:
- # 执行删除操作
- delete_todo(todo_id)
- debug_message += f"Deleted todo with ID: {todo_id}\n"
- except Exception as e:
- # 记录删除错误
- logging.error(f"Error deleting todo {todo_id}: {e}")
- debug_message += f"Failed to delete todo {todo_id}: {e}\n"
- # 处理添加新的待办事项
- if add_clicks and title:
- # 创建新的待办事项
- add_todo(title, description or '')
- # 清空输入框
- title = None
- description = None
- # 处理待办事项状态切换
- for value, status_id in zip(status_values, status_ids):
- todo_id = status_id['index']
-
- # 灵活处理不同类型的状态值
- if isinstance(value, list):
- completed = 1 in value # 对于复选框
- else:
- completed = bool(value) # 对于开关
-
- logging.debug(f"Updating todo {todo_id} status to {completed}")
- # 更新待办事项状态
- update_todo_status(todo_id, completed)
- debug_message += f"Updated todo {todo_id} status to {completed}\n"
- # 获取当前页的待办事项
- page = active_page or 1
- per_page = 10
- todos, total_todos = get_todos(page, per_page)
-
- # 计算总页数
- total_pages = max(1, (total_todos + per_page - 1) // per_page)
- debug_message += f"Total todos: {total_todos}, Total pages: {total_pages}\n"
- # 渲染待办事项列表
- todo_list = render_todo_list(todos)
-
- # 返回更新后的组件状态
- return [todo_list, total_pages, title, description, debug_message]
- if __name__ == '__main__':
- app.run_server(debug=True)
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |