python 的框架 dash 开发TodoList Web 应用

打印 上一主题 下一主题

主题 683|帖子 683|积分 2049

TodoList Web 应用


项目简介

这是一个基于 Dash 和 SQLAlchemy 的现代化 TodoList Web 应用,提供了简朴而强大的待办事项管理功能。
主要特性



  • 添加新的待办事项
  • 删除待办事项
  • 标记待办事项为已完成/未完成
  • 分页展示待办事项列表
  • 实时更新和交互
技术栈



  • Python
  • Dash (Web框架)
  • SQLAlchemy (ORM)
  • SQLite (数据库)
  • Dash Bootstrap Components (UI组件)
功能详细分析

待办事项管理

应用提供了一个统一的回调函数 manage_todos(),处理以下交互逻辑:

  • 删除待办事项
  • 添加新的待办事项
  • 切换待办事项状态
  • 分页展示待办事项列表
删除功能



  • 支持通过删除按钮移除指定的待办事项
  • 添加了详细的错误处理和日志记录
  • 确保只处理有效的点击变乱
状态切换



  • 支持多种状态切换方式(复选框和开关)
  • 灵活处理差别范例的状态值
  • 实时更新待办事项完成状态
分页



  • 默认每页显示10个待办事项
  • 动态盘算总页数
  • 支持页面间切换
调试与日志

应用内置详细的日志记录机制,记录:


  • 回调上下文详情
  • 触发的输入变乱
  • 操纵执行情况
  • 错误信息
运行项目

依赖安装

  1. pip install -r requirements.txt
复制代码
启动应用

  1. python app.py
复制代码
访问 http://127.0.0.1:8050/ 利用应用
requirements.txt
‘’’
dash2.14.1
dash-bootstrap-components1.5.0
sqlalchemy2.0.25
plotly5.19.0
‘’’
  1. import dash
  2. import dash_bootstrap_components as dbc
  3. from dash import html, dcc, Input, Output, State, dash_table
  4. from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime
  5. from sqlalchemy.orm import sessionmaker, declarative_base
  6. from sqlalchemy.sql import func
  7. from datetime import datetime
  8. import logging
  9. import json  # 确保导入 json 模块
  10. # 配置日志
  11. logging.basicConfig(level=logging.DEBUG,
  12.                     format='%(asctime)s - %(levelname)s: %(message)s')
  13. # 数据库配置
  14. DATABASE_URL = 'sqlite:///todolist.db'
  15. engine = create_engine(DATABASE_URL)
  16. SessionLocal = sessionmaker(bind=engine)
  17. Base = declarative_base()
  18. # 待办事项模型
  19. class Todo(Base):
  20.     __tablename__ = 'todos'
  21.    
  22.     id = Column(Integer, primary_key=True, index=True)
  23.     title = Column(String, index=True)
  24.     description = Column(String, nullable=True)
  25.     completed = Column(Boolean, default=False)
  26.     created_at = Column(DateTime, server_default=func.now())
  27.     updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
  28. # 创建数据库表
  29. Base.metadata.create_all(bind=engine)
  30. # 初始化 Dash 应用
  31. app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
  32. def get_todos(page=1, per_page=10):
  33.     """获取分页后的待办事项"""
  34.     db = SessionLocal()
  35.     try:
  36.         total_todos = db.query(Todo).count()
  37.         offset = (page - 1) * per_page
  38.         todos = db.query(Todo).order_by(Todo.created_at.desc()).offset(offset).limit(per_page).all()
  39.         logging.debug(f"Total todos: {total_todos}, Page: {page}, Per page: {per_page}")
  40.         return todos, total_todos
  41.     finally:
  42.         db.close()
  43. def add_todo(title, description):
  44.     """添加新的待办事项"""
  45.     db = SessionLocal()
  46.     try:
  47.         new_todo = Todo(title=title, description=description)
  48.         db.add(new_todo)
  49.         db.commit()
  50.         db.refresh(new_todo)
  51.         logging.debug(f"Added new todo: {new_todo.title}")
  52.         return new_todo
  53.     finally:
  54.         db.close()
  55. def update_todo_status(todo_id, completed):
  56.     """更新待办事项状态"""
  57.     db = SessionLocal()
  58.     try:
  59.         todo = db.query(Todo).filter(Todo.id == todo_id).first()
  60.         if todo:
  61.             todo.completed = completed
  62.             db.commit()
  63.             logging.debug(f"Todo {todo_id} status updated to {completed}")
  64.     except Exception as e:
  65.         logging.error(f"Error updating todo status: {e}")
  66.     finally:
  67.         db.close()
  68. def delete_todo(todo_id):
  69.     """删除待办事项"""
  70.     db = SessionLocal()
  71.     try:
  72.         todo = db.query(Todo).filter(Todo.id == todo_id).first()
  73.         if todo:
  74.             db.delete(todo)
  75.             db.commit()
  76.     finally:
  77.         db.close()
  78. def render_todo_list(todos):
  79.     """渲染待办事项列表"""
  80.     todo_list = []
  81.     for todo in todos:
  82.         todo_list.append(
  83.             dbc.Card([
  84.                 dbc.CardBody([
  85.                     html.H5(todo.title, className="card-title"),
  86.                     html.P(todo.description or '', className="card-text"),
  87.                     dbc.Checklist(
  88.                         options=[{'label': '已完成', 'value': 1}],
  89.                         value=[1] if todo.completed else [],
  90.                         id={'type': 'todo-status', 'index': todo.id},
  91.                         switch=True
  92.                     ),
  93.                     dbc.Button("删除", color="danger", size="sm",
  94.                                id={'type': 'delete-todo', 'index': todo.id})
  95.                 ])
  96.             ], className="mb-2", id=f'todo-card-{todo.id}')
  97.         )
  98.     return todo_list
  99. # 应用布局
  100. app.layout = dbc.Container([
  101.     html.H1("TodoList 应用", className="text-center my-4"),
  102.    
  103.     # 添加待办事项表单
  104.     dbc.Row([
  105.         dbc.Col([
  106.             dbc.Label("标题"),
  107.             dbc.Input(id='todo-title', type='text', placeholder='输入待办事项标题')
  108.         ], width=12),
  109.     ], className="mb-3"),
  110.    
  111.     dbc.Row([
  112.         dbc.Col([
  113.             dbc.Label("描述"),
  114.             dbc.Input(id='todo-description', type='text', placeholder='输入待办事项描述')
  115.         ], width=12)
  116.     ], className="mb-3"),
  117.    
  118.     dbc.Row([
  119.         dbc.Col([
  120.             dbc.Button("添加待办", id='add-todo-button', color='primary')
  121.         ])
  122.     ], className="mb-3"),
  123.    
  124.     # 待办事项列表
  125.     html.Div(id='todo-list-container'),
  126.    
  127.     # 分页组件
  128.     dbc.Pagination(
  129.         id='todo-pagination',
  130.         max_value=1,  # 初始化为1
  131.         fully_expanded=False
  132.     ),
  133.     # 调试信息显示
  134.     html.Div(id='debug-output')
  135. ], fluid=True)
  136. # 初始化加载待办事项
  137. @app.callback(
  138.     [Output('todo-list-container', 'children'),
  139.      Output('todo-pagination', 'max_value')],
  140.     [Input('todo-pagination', 'active_page')]
  141. )
  142. def initial_load(active_page):
  143.     """初始加载待办事项"""
  144.     page = active_page or 1
  145.     per_page = 10
  146.     todos, total_todos = get_todos(page, per_page)
  147.    
  148.     # 计算总页数
  149.     total_pages = max(1, (total_todos + per_page - 1) // per_page)
  150.    
  151.     # 渲染待办事项列表
  152.     todo_list = render_todo_list(todos)
  153.    
  154.     return [todo_list, total_pages]
  155. @app.callback(
  156.     [Output('todo-list-container', 'children', allow_duplicate=True),
  157.      Output('todo-pagination', 'max_value', allow_duplicate=True),
  158.      Output('todo-title', 'value'),
  159.      Output('todo-description', 'value'),
  160.      Output('debug-output', 'children')],
  161.     [Input('todo-pagination', 'active_page'),
  162.      Input('add-todo-button', 'n_clicks'),
  163.      Input({'type': 'delete-todo', 'index': dash.ALL}, 'n_clicks'),
  164.      Input({'type': 'todo-status', 'index': dash.ALL}, 'value')],
  165.     [State('todo-title', 'value'),
  166.      State('todo-description', 'value'),
  167.      State({'type': 'delete-todo', 'index': dash.ALL}, 'id'),
  168.      State({'type': 'todo-status', 'index': dash.ALL}, 'id')],
  169.     prevent_initial_call=True
  170. )
  171. def manage_todos(active_page, add_clicks, delete_clicks, status_values,
  172.                  title, description, delete_ids, status_ids):
  173.     """
  174.     统一管理待办事项的增删改查操作
  175.    
  176.     这个回调函数处理所有的交互逻辑:
  177.     1. 删除待办事项
  178.     2. 添加新的待办事项
  179.     3. 切换待办事项状态
  180.     4. 分页展示待办事项列表
  181.    
  182.     参数说明:
  183.     - active_page: 当前分页页码
  184.     - add_clicks: 添加按钮点击次数
  185.     - delete_clicks: 删除按钮点击次数列表
  186.     - status_values: 状态切换值列表
  187.     - title, description: 新建待办事项的标题和描述
  188.     - delete_ids, status_ids: 对应的待办事项ID
  189.     """
  190.     ctx = dash.callback_context
  191.    
  192.     # 记录详细的调试日志,帮助追踪回调上下文
  193.     logging.debug("Callback Context Details:")
  194.     logging.debug(f"Triggered Inputs: {ctx.triggered}")
  195.     logging.debug(f"Input Values:")
  196.     logging.debug(f"Active Page: {active_page}")
  197.     logging.debug(f"Add Clicks: {add_clicks}")
  198.     logging.debug(f"Delete Clicks: {delete_clicks}")
  199.     logging.debug(f"Delete IDs: {delete_ids}")
  200.     logging.debug(f"Status Values: {status_values}")
  201.     logging.debug(f"Status IDs: {status_ids}")
  202.    
  203.     # 初始加载:如果没有触发任何事件,默认加载第一页
  204.     if not ctx.triggered:
  205.         page = 1
  206.         per_page = 10
  207.         todos, total_todos = get_todos(page, per_page)
  208.         total_pages = max(1, (total_todos + per_page - 1) // per_page)
  209.         todo_list = render_todo_list(todos)
  210.         return [todo_list, total_pages, title, description, '']
  211.     # 初始化调试消息
  212.     triggered = ctx.triggered
  213.     debug_message = "Triggered Inputs:\n"
  214.     # 处理删除待办事项
  215.     for n_clicks, delete_id in zip(delete_clicks, delete_ids):
  216.         logging.debug(f"Delete: n_clicks={n_clicks}, delete_id={delete_id}")
  217.         
  218.         # 确保是有效的点击事件(非初始 None 值)
  219.         if n_clicks is not None and n_clicks > 0:
  220.             todo_id = delete_id['index']
  221.             logging.debug(f"Attempting to delete todo with ID: {todo_id}")
  222.             
  223.             try:
  224.                 # 执行删除操作
  225.                 delete_todo(todo_id)
  226.                 debug_message += f"Deleted todo with ID: {todo_id}\n"
  227.             except Exception as e:
  228.                 # 记录删除错误
  229.                 logging.error(f"Error deleting todo {todo_id}: {e}")
  230.                 debug_message += f"Failed to delete todo {todo_id}: {e}\n"
  231.     # 处理添加新的待办事项
  232.     if add_clicks and title:
  233.         # 创建新的待办事项
  234.         add_todo(title, description or '')
  235.         # 清空输入框
  236.         title = None
  237.         description = None
  238.     # 处理待办事项状态切换
  239.     for value, status_id in zip(status_values, status_ids):
  240.         todo_id = status_id['index']
  241.         
  242.         # 灵活处理不同类型的状态值
  243.         if isinstance(value, list):
  244.             completed = 1 in value  # 对于复选框
  245.         else:
  246.             completed = bool(value)  # 对于开关
  247.         
  248.         logging.debug(f"Updating todo {todo_id} status to {completed}")
  249.         # 更新待办事项状态
  250.         update_todo_status(todo_id, completed)
  251.         debug_message += f"Updated todo {todo_id} status to {completed}\n"
  252.     # 获取当前页的待办事项
  253.     page = active_page or 1
  254.     per_page = 10
  255.     todos, total_todos = get_todos(page, per_page)
  256.    
  257.     # 计算总页数
  258.     total_pages = max(1, (total_todos + per_page - 1) // per_page)
  259.     debug_message += f"Total todos: {total_todos}, Total pages: {total_pages}\n"
  260.     # 渲染待办事项列表
  261.     todo_list = render_todo_list(todos)
  262.    
  263.     # 返回更新后的组件状态
  264.     return [todo_list, total_pages, title, description, debug_message]
  265. if __name__ == '__main__':
  266.     app.run_server(debug=True)
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

李优秀

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表