环境安装
Python 安装
下载地址:https://www.python.org/downloads/,下载版本为 3.6+ 即可。示例地址:https://www.python.org/downloads/release/python-386/。可在列表中根据自己使用机器进行选择性下载。
Pycharm 安装
下载地址:https://www.jetbrains.com/pycharm/download/#section=windows
Pycharm 激活
如果下载的是 Professional 版本的 Pycharm ,那么将 Pycharm 安装完成之后需要进行激活操作,激活方式可以分为账号激活,激活码激活,激活URL等方式,这里采用激活码的方式进行。文档地址:https://github.com/wenyanjun/free。为了方便,已将激活码复制到地址 https://paste.ubuntu.com/p/XSfKGFFFt3/ ,只需复制激活码到 Pycharm 中激活即可。
ToDoList
在线体验地址 http://www.todolist.cn/
需求分析
添加 ToDo
在添加 ToDo 输入框中,输入待办的 ToDo,那么该 ToDo 就会添加到正在进行的列表中
完成 ToDo
勾选 ”正在进行“ 中的待办 ToDo,那么就修改该 ToDo 的状态为已完成
修改 ToDo
可将 ”正在进行“ 中的待办 ToDo 的状态修改为 “已经完成”
可以修改 “正在进行” 中的待办的 ToDo 的内容。例如将 “今晚去游泳” 修改为 “今晚去聚餐”
已经完成的 ToDo 不能进行修改,只能被删除
删除 ToDo
可对正在进行或者已经完成的 ToDo 进行删除
删除所有 ToDo
支持一键删除所有的 ToDo,包含 “正在进行” 和 “已经完成” 两种状态的
获取所有 ToDo
需要提供查询接口,查询所有的 ToDo,或者单独查询 “正在进行” 的 ToDo,或者单独查询 “已经完成” 的 ToDo
模块拆分
在本项目中因为只涉及到 ToDo 的操作,所以可以将 ToDo 看作一个模块,只需开发该模块的增删改查接口即可。模块拆分的原则可参考系统架构设计模块拆分维度和原则, 模块拆分案例 - 5
问题:如果增加用户登录,注册,注销。评论等功能,那么该怎么拆分模块呢?
系统设计
项目结构设计
1 2 3 4 5
| │─tolist │ app.py # 应用文件 │ database.py # 数据库连接 │ models.py # 数据库实体 │ test.db # 数据库文件
|
数据库设计
1 2 3 4 5 6 7 8 9
| create table todo ( id INTEGER not null primary key, name VARCHAR(50) not null, status SMALLINT not null, deleted BOOLEAN not null, check (deleted IN (0, 1)) );
|
ToDo 实体类设计
每个 ToDo 实体都有状态(正在进行、已经完成),名称,为了能唯一标识一个 ToDo,还可以为每个 ToDo 分配一个 ID,即实体类设计如下:
1 2 3 4 5
| class ToDo(Base): __tablename__ = 'todo' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(50), default="", nullable=False) status = Column(SMALLINT, default=0, nullable=False)
|
需求开发
Flask 入门
打开 Pycharm,选择 new project 新建一个 Flask 项目
项目搭建
上述创建一个 Flask 项目后,即可生成一个最简单的 Flask 应用,其中 app.py 内容如下:
1 2 3 4 5 6 7 8 9 10 11 12
| from flask import Flask
app = Flask(__name__)
@app.route('/') def hello_world(): return 'Hello World!'
if __name__ == '__main__': app.run()
|
新建 database.py 文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12
| """ https://dormousehole.readthedocs.io/en/latest/patterns/sqlalchemy.html """
from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:///test.db', convert_unicode=True) db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine)) Base = declarative_base() Base.query = db_session.query_property()
|
新建 models.py 文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from sqlalchemy import Column, Integer, String, SMALLINT from database import Base, engine
class ToDo(Base): __tablename__ = 'todo' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(50), default="", nullable=False) status = Column(SMALLINT, default=0, nullable=False)
def __init__(self, name=None, status=None): self.name = name self.status = status
def __repr__(self): return '<ToDo %r>' % (self.name)
def init_db(): Base.metadata.create_all(bind=engine)
if __name__ == '__main__': init_db()
|
接口开发
添加 ToDo
1 2 3 4 5 6 7 8 9 10 11 12 13
| @app.route('/todo', methods=["POST"]) def create_todo(): params = request.json if params: name = params.get("name") todo = ToDo(name=name, status=0) db_session.add(todo) db_session.commit() return {"status": 1, "message": "success"} else: return {"status": 1, "message": "params error"}, 401
|
完成 ToDo/修改 ToDo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @app.route('/todo/<string:todo_id>', methods=["PUT"]) def update_todo(todo_id): data = {} params = request.json if params: if params.get("status") is not None: data.update({"status": bool(params.get("status"))}) if params.get("name") is not None: data.update({"name": params.get("name")}) try: rows = db_session.query(ToDo).filter(ToDo.id == todo_id, ToDo.deleted != True).update(data) if not rows: return {"status": 0, "message": f"update error"}, 401 db_session.commit() except Exception as e: return {"status": 0, "message": f"update error: {e}"}, 401 return {"status": 1, "message": "success"} else: return {"status": 1, "message": "params error"}, 401
|
删除 ToDo/删除所有 ToDo
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @app.route('/todo/', methods=["DELETE"]) @app.route('/todo/<string:todo_id>', methods=["DELETE"]) def delete_todo(todo_id=None): try: if todo_id: rows = db_session.query(ToDo).filter(ToDo.id == todo_id).update({"deleted": True}) else: rows = db_session.query(ToDo).update({"deleted": True}) if not rows: return {"status": 0, "message": "delete error"}, 401 db_session.commit() except Exception as e: return {"status": 0, "message": f"delete error: {e}"}, 401 return {"status": 1, "message": "success"}
|
获取所有 ToDo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @app.route('/todo/<string:todo_id>', methods=["PUT"]) def update_todo(todo_id): data = {} params = request.json if params: if params.get("status") is not None: data.update({"status": bool(params.get("status"))}) if params.get("name") is not None: data.update({"name": params.get("name")}) try: rows = db_session.query(ToDo).filter(ToDo.id == todo_id, ToDo.deleted != True).update(data) if not rows: return {"status": 0, "message": f"update error"}, 401 db_session.commit() except Exception as e: return {"status": 0, "message": f"update error: {e}"}, 401 return {"status": 1, "message": "success"} else: return {"status": 1, "message": "params error"}, 401
|
功能测试
使用 postman 进行功能性测试,下载地址 https://www.postman.com/, chrome 插件地址: https://chrome.google.com/webstore/detail/postman-interceptor/aicmkgpgakddgnaphhhpliifpcfhicfo?utm_source=chrome-ntp-icon
源码信息
可在此处下载上述源码:https://raw.githubusercontent.com/rexyan/warehouse/master/tolist.zip
参考资料来源
ToDoList: http://www.todolist.cn/
Python官网:https://www.python.org
模块拆分案例 - 5:https://zhuanlan.zhihu.com/p/172528871
系统架构设计模块拆分维度和原则:https://blog.csdn.net/varyall/article/details/81461002
###