Flask入门实战:从零构建Python Web应用与Todo List项目

发布时间:2026/7/30 3:56:03
Flask入门实战:从零构建Python Web应用与Todo List项目 1. 项目概述为什么选择Flask作为你的第一个Web项目如果你刚接触Python想试试看怎么让代码跑在浏览器里或者你是个后端开发者想快速搭个接口给前端用Flask几乎是你绕不开的第一个选择。它不像Django那样自带“全家桶”一上来就给你一堆文件夹和配置文件让人有点懵。Flask的核心哲学是“微”这意味着它只提供最基础、最核心的Web服务能力比如路由、请求响应处理、模板渲染。剩下的你需要什么再去安装对应的扩展库比如操作数据库用Flask-SQLAlchemy处理表单用Flask-WTF。这种“按需取用”的方式让你能清晰地理解Web应用的每一块“积木”是怎么拼起来的而不是被框架的复杂性淹没。我见过很多新手一上来就想用Django做个博客结果光理解MTVModel-Template-View结构和那一堆命令行操作就花了大量时间真正想实现的核心功能反而没怎么写。用Flask就简单直接得多一个app.py文件几行代码一个最简单的“Hello, World!”页面就出来了。这种即时的正反馈对学习至关重要。它能让你快速建立起“请求-响应”的直观感受用户在浏览器输入一个网址发送请求你的Flask程序接收到这个请求执行对应的Python函数视图函数最后返回一段HTML文本响应浏览器再把这段文本渲染成页面。整个链路清晰可见。所以这个“简易的web端程序”项目目标不是做一个功能多复杂的系统而是带你完整地走通这个链路。我们会从零开始搭建环境、写第一个页面、处理表单提交、连接数据库用轻量级的SQLite最后把它运行起来。你会得到一个具备基础增删改查CRUD功能的待办事项Todo ListDemo。这个Demo麻雀虽小五脏俱全涵盖了大部分Web开发的核心概念。完成后你完全可以用这个模式去扩展成个人博客、简单的数据管理后台等等。2. 环境准备与项目初始化动手之前得先把“厨房”收拾好。这里我会详细到每一个步骤包括可能遇到的坑和解决办法确保你一次成功。2.1 Python环境与虚拟隔离首先确保你的电脑上安装了Python。打开命令行Windows上是CMD或PowerShellMac/Linux上是Terminal输入python --version或python3 --version。如果能看到像Python 3.8.10这样的版本号并且版本是3.6以上那就没问题。如果提示“不是内部或外部命令”你需要去Python官网下载安装。安装时务必勾选“Add Python to PATH”这样系统才能找到它。注意强烈不建议使用系统自带的Python特别是macOS也尽量不要在全局环境直接安装项目依赖。不同项目可能需要不同版本的库混在一起会引发依赖冲突问题极难排查。因此我们要使用虚拟环境Virtual Environment。它就像一个独立的“沙盒”在这个沙盒里安装的包只对当前项目有效不会影响系统或其他项目。这是Python项目开发的最佳实践务必养成习惯。创建虚拟环境的命令因操作系统略有不同Windows:python -m venv venvMac/Linux:python3 -m venv venv这行命令会在当前目录下创建一个名为venv的文件夹里面就是独立的Python环境。接下来要激活它Windows (CMD):venv\Scripts\activate.batWindows (PowerShell):venv\Scripts\Activate.ps1如果执行策略禁止可以先以管理员身份运行Set-ExecutionPolicy RemoteSignedMac/Linux:source venv/bin/activate激活成功后你的命令行提示符前面通常会显示(venv)表示你已经在这个虚拟环境里了。之后所有pip install的操作都会把包装到venv目录下。2.2 安装Flask与核心依赖环境激活后就可以安装Flask了。在虚拟环境下执行pip install flaskpip是Python的包管理工具这条命令会从PyPIPython官方的软件仓库下载Flask及其必要的依赖如Jinja2模板引擎、Werkzeug WSGI工具集。为了我们后续的Demo还需要安装两个扩展pip install flask-sqlalchemyflask-sqlalchemy是一个ORM对象关系映射库的Flask集成版。ORM能让你用Python类和对象的方式来操作数据库而不用直接写复杂的SQL语句。对于新手来说这大大降低了数据库操作的门槛。我们用它来连接SQLite数据库。安装完成后可以创建一个requirements.txt文件来记录项目依赖方便以后部署或协作pip freeze requirements.txt这个文件里会列出所有已安装的包及其精确版本。别人拿到你的项目代码只需要运行pip install -r requirements.txt就能一键安装所有依赖。2.3 项目结构规划虽然是个简易项目但好的结构习惯要从开始培养。我们不搞Django那种复杂的自动生成就手动创建几个最必要的文件和文件夹。在你的项目根目录下和venv文件夹同级创建如下结构my_flask_demo/ ├── app.py # 主程序入口 ├── requirements.txt # 依赖列表 ├── static/ # 静态文件CSS, JavaScript, 图片 │ └── style.css ├── templates/ # HTML模板文件 │ ├── base.html # 基础模板 │ ├── index.html # 首页 │ └── edit.html # 编辑页面 └── instance/ # 实例文件夹存放数据库文件等app.py这是Flask应用的核心所有路由和逻辑都写在这里起步。static/存放浏览器直接访问的文件比如让页面变漂亮的CSS或者实现交互的JavaScript。templates/存放Jinja2模板文件。模板就是HTML文件里嵌入了Python变量和逻辑Flask负责把数据和模板结合生成最终的HTML。instance/Flask建议把一些属于特定实例比如你本机开发的配置、数据库文件放在这里与代码分离。3. 核心代码实现与逐行解析接下来我们进入核心环节一步步构建我们的Todo List应用。我会把代码拆开详细解释每一部分的作用和背后的原理。3.1 应用初始化与配置打开app.py我们从最基础的骨架开始写起# app.py from flask import Flask, render_template, request, redirect, url_for, flash from flask_sqlalchemy import SQLAlchemy import os # 1. 创建Flask应用实例 app Flask(__name__) # 2. 配置数据库 # 使用instance文件夹内的sqlite数据库文件 basedir os.path.abspath(os.path.dirname(__file__)) app.config[SQLALCHEMY_DATABASE_URI] sqlite:/// os.path.join(basedir, instance, todo.db) app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False app.config[SECRET_KEY] dev-key-please-change-in-production # 用于flash消息的加密生产环境必须更换 # 3. 创建数据库对象 db SQLAlchemy(app)逐行解析导入模块Flask是核心类。render_template用于渲染HTML模板。request用来获取用户请求中的数据如表单内容。redirect和url_for用于页面跳转url_for能根据视图函数名生成对应的URL比硬编码URL更灵活安全。flash用于在页面间传递一次性提示消息比如“添加成功”。创建实例__name__是一个Python特殊变量代表当前模块的名字。Flask需要它来确定应用的位置以便寻找模板、静态文件等资源。配置数据库URISQLALCHEMY_DATABASE_URI告诉Flask-SQLAlchemy数据库在哪里。这里我们使用SQLite它是一种文件型数据库无需安装服务器一个.db文件就是整个数据库非常适合开发和演示。路径指向了instance/todo.db文件。关闭追踪修改SQLALCHEMY_TRACK_MODIFICATIONS设置为False是为了避免在不需要Flask-SQLAlchemy的事件系统时产生不必要的开销和警告。这是一个性能优化项通常都建议关闭。设置密钥SECRET_KEY是Flask用于加密会话session和flash消息的密钥。在开发环境可以用一个简单的字符串但一旦要部署到公网必须换成一个足够长且随机的复杂字符串并且绝不能提交到代码仓库这是安全底线。初始化数据库对象用配置好的app对象来创建db对象之后我们定义数据模型和进行操作都会用到它。3.2 定义数据模型模型Model对应数据库中的表。我们用Python类来定义它。# app.py (接上文) class Todo(db.Model): 待办事项数据模型 id db.Column(db.Integer, primary_keyTrue) # 主键唯一标识 title db.Column(db.String(100), nullableFalse) # 标题非空最大100字符 is_completed db.Column(db.Boolean, defaultFalse) # 完成状态默认为未完成 created_at db.Column(db.DateTime, defaultdb.func.current_timestamp()) # 创建时间 def __repr__(self): return fTodo {self.title}逐行解析db.Model所有模型类都必须继承自db.Model。db.Column定义表的一个字段列。db.Integer、db.String、db.Boolean、db.DateTime定义了字段的数据类型。primary_keyTrue将此字段设为主键。主键的值必须唯一通常用于快速查找和关联。nullableFalse约束此字段不能为空NULL。如果尝试存入空值数据库会报错。default设置字段的默认值。db.func.current_timestamp()是SQLAlchemy提供的函数用于获取当前时间戳。__repr__方法这是一个“官方”的字符串表示方法在调试时非常有用。比如在Python交互环境打印一个Todo对象时会显示Todo 买牛奶而不是晦涩的内存地址。3.3 创建数据库表模型定义好了但数据库里还没有对应的表。我们需要创建它们。一种方式是在Python交互式命令行里执行db.create_all()。但更常见的做法是写一个简单的脚本或者直接在应用启动前检查并创建。我们在app.py末尾添加# app.py (接上文) # 4. 创建数据库表如果不存在 with app.app_context(): db.create_all()with app.app_context():这行代码创建了一个应用上下文。Flask的很多操作比如db.create_all()都需要在应用上下文中才能执行。这确保了数据库操作能正确关联到我们创建的app实例。实操心得很多新手会直接写db.create_all()然后运行报错提示“在应用上下文之外”。记住任何涉及db或需要访问app.config的操作如果不在视图函数内视图函数自动有上下文就需要手动用app.app_context()包裹起来。这是Flask上下文机制的一个关键点。3.4 编写视图函数处理请求视图函数View Function是Flask应用的核心。它绑定一个URL规则路由当用户访问这个URL时对应的函数就会被执行并返回响应。3.4.1 首页展示所有待办事项# app.py (接上文) app.route(/) def index(): 首页展示所有待办事项 # 从数据库查询所有Todo项按创建时间倒序排列新的在前 todo_list Todo.query.order_by(Todo.created_at.desc()).all() # 将查询结果和模板一起传给render_template它会渲染出最终的HTML return render_template(index.html, todo_listtodo_list)app.route(/)这是一个装饰器。它把下面的index函数注册为处理根路径/即网站首页的视图函数。Todo.query这是Flask-SQLAlchemy提供的查询接口非常直观。.order_by(Todo.created_at.desc())对结果按created_at字段进行降序desc排序。.all()执行查询并返回所有结果的列表。如果只想取第一条可以用.first()。render_template(index.html, todo_listtodo_list)这是关键。它会去templates文件夹下找到index.html文件然后把todo_list这个变量传递进去。在index.html模板里我们就可以用Jinja2语法来循环显示这个列表了。3.4.2 添加新待办事项# app.py (接上文) app.route(/add, methods[POST]) def add_todo(): 处理添加新待办事项的表单提交 # 从POST请求的表单数据中获取‘title’字段的值 title request.form.get(title) if not title or title.strip() : # 如果标题为空设置一个flash错误消息 flash(待办事项标题不能为空, error) else: # 创建一个新的Todo对象 new_todo Todo(titletitle.strip()) # 将其添加到数据库会话中 db.session.add(new_todo) # 提交会话将数据真正写入数据库 db.session.commit() # 设置一个flash成功消息 flash(待办事项添加成功, success) # 无论成功失败都重定向回首页 return redirect(url_for(index))methods[POST]默认路由只响应GET请求。这里我们指定它也响应POST请求因为表单提交通常用POST方法这样更安全数据在请求体内不会显示在URL中。request.form一个类似字典的对象包含了POST请求中表单提交的所有数据。.get(title)安全地获取名为title的字段值如果不存在则返回None。flash()消息闪现。它会把消息存储在session中只在下一次请求时可用读取后即被清除。非常适合用来显示“操作成功”或“出错了”这类一次性提示。第二个参数是消息类别如‘success‘ ’error‘可以用来在模板中设置不同的样式。db.session代表数据库会话。所有对数据库的改动增、删、改都需要先添加到会话中最后通过commit()一次性提交。这保证了操作的原子性要么全部成功要么全部回滚。redirect(url_for(index))操作完成后将用户的浏览器重定向到首页。url_for(index)会动态生成首页的URL即/这样即使你以后改了路由规则这里的代码也不用变。3.4.3 切换完成状态与删除# app.py (接上文) app.route(/toggle/int:todo_id) def toggle_todo(todo_id): 切换待办事项的完成状态 # 根据URL中的todo_id查询对应的Todo对象如果没有则返回404 todo Todo.query.get_or_404(todo_id) # 取反当前的完成状态 todo.is_completed not todo.is_completed db.session.commit() flash(状态已更新, info) return redirect(url_for(index)) app.route(/delete/int:todo_id) def delete_todo(todo_id): 删除待办事项 todo Todo.query.get_or_404(todo_id) db.session.delete(todo) # 从会话中标记删除 db.session.commit() flash(待办事项已删除。, warning) return redirect(url_for(index))/toggle/int:todo_id这是一个动态路由。int:todo_id表示URL的这一部分会被捕获并转换为整数int然后作为参数todo_id传递给视图函数。例如访问/toggle/3todo_id的值就是3。Todo.query.get_or_404(todo_id)get()方法通过主键查询。get_or_404()是它的一个便捷版本如果找不到对应ID的对象它会自动中止请求并返回一个404 Not Found错误页面省去了我们手动判断的代码。db.session.delete(todo)将对象标记为待删除。3.5 创建HTML模板Flask使用Jinja2作为模板引擎。模板就是普通的HTML文件里面可以插入变量和简单的逻辑。我们先创建一个基础模板templates/base.html其他页面可以继承它避免重复写HTML骨架。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}简易待办清单{% endblock %}/title link relstylesheet href{{ url_for(static, filenamestyle.css) }} link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/css/bootstrap.min.css relstylesheet /head body classbg-light div classcontainer mt-5 h1 classmb-4 text-center我的简易待办清单/h1 !-- Flash消息展示区 -- {% with messages get_flashed_messages(with_categoriestrue) %} {% if messages %} {% for category, message in messages %} div classalert alert-{{ danger if category error else category }} alert-dismissible fade show rolealert {{ message }} button typebutton classbtn-close>{% extends base.html %} {% block content %} div classcard shadow div classcard-body !-- 添加新事项的表单 -- form action{{ url_for(add_todo) }} methodPOST classrow g-2 mb-4 div classcol-auto input typetext nametitle classform-control placeholder输入新的待办事项... required /div div classcol-auto button typesubmit classbtn btn-primary添加/button /div /form !-- 待办事项列表 -- {% if todo_list %} ul classlist-group {% for todo in todo_list %} li classlist-group-item d-flex justify-content-between align-items-center div !-- 根据完成状态显示不同的复选框和文字样式 -- form action{{ url_for(toggle_todo, todo_idtodo.id) }} methodGET classd-inline button typesubmit classbtn btn-sm btn-link p-0 me-2 {% if todo.is_completed %} span classtext-success✅/span {% else %} span classtext-secondary⬜/span {% endif %} /button /form span class{% if todo.is_completed %}text-decoration-line-through text-muted{% endif %} {{ todo.title }} /span small classtext-muted ms-2{{ todo.created_at.strftime(%Y-%m-%d %H:%M) }}/small /div div !-- 删除按钮 -- a href{{ url_for(delete_todo, todo_idtodo.id) }} classbtn btn-sm btn-outline-danger onclickreturn confirm(确定要删除“{{ todo.title }}”吗); 删除 /a /div /li {% endfor %} /ul {% else %} p classtext-center text-muted暂无待办事项添加一个吧/p {% endif %} /div /div {% endblock %}{% extends base.html %}声明此模板继承自base.html。action{{ url_for(add_todo) }}表单提交的目标URL由url_for动态生成。requiredHTML5属性浏览器会在提交前检查输入框是否为空。{% for todo in todo_list %}循环遍历从视图函数传递过来的todo_list变量。todo.created_at.strftime(%Y-%m-%d %H:%M)在模板中直接调用Python对象的strftime方法格式化时间显示。onclickreturn confirm(...)简单的JavaScript确认对话框防止误删。3.6 添加一点样式为了让页面好看点我们在static/style.css里加一点自定义样式/* static/style.css */ body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif; } .list-group-item:hover { background-color: #f8f9fa; } .text-decoration-line-through { text-decoration: line-through; } /* 给完成的事项一个更淡的背景色 */ .list-group-item .text-muted { opacity: 0.6; }4. 运行与调试你的Flask应用代码写完了怎么让它跑起来呢回到命令行确保你在项目根目录且虚拟环境已激活。4.1 启动开发服务器在app.py所在目录运行flask run或者python app.py如果后者不行通常用flask run你会看到类似这样的输出* Serving Flask app app.py * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. * Running on http://127.0.0.1:5000现在打开你的浏览器访问http://127.0.0.1:5000你的第一个Flask Web应用就出现了你可以尝试添加、完成、删除待办事项。4.2 开启调试模式默认情况下flask run使用的是生产模式代码修改后不会自动重载出错也只会显示简单的错误页面。对于开发我们需要开启调试模式。有两种方法设置环境变量推荐更灵活Windows (CMD):set FLASK_DEBUG1Windows (PowerShell):$env:FLASK_DEBUG1Mac/Linux:export FLASK_DEBUG1设置之后再运行flask run。在代码中设置app.py里if __name__ __main__: app.run(debugTrue)然后使用python app.py运行。调试模式的好处自动重载当你修改代码并保存后服务器会自动重启无需手动停止再启动。详细的错误页面如果程序出错浏览器会显示一个交互式的错误页面告诉你错误发生在哪一行甚至可以在网页上执行一些简单的调试命令Pin码保护。这对定位BUG至关重要。重要警告调试模式会向公众暴露大量你的代码和服务器信息绝对不要在生产环境即公网服务器开启调试模式否则会带来严重的安全风险。5. 常见问题与排查技巧实录在实际操作中你几乎一定会遇到下面这些问题。我把它们和解决方法整理出来你可以像查字典一样使用。5.1 导入错误与模块找不到问题运行flask run或python app.py时报错ModuleNotFoundError: No module named flask。原因最常见的原因是没有在虚拟环境下操作或者虚拟环境没有正确激活。排查检查命令行提示符前是否有(venv)字样。在命令行输入pip list查看输出的列表中是否有Flask。如果没有说明没在虚拟环境里安装。如果确认在虚拟环境但依然报错尝试用绝对路径指定Python解释器/path/to/your/project/venv/bin/python app.py(Mac/Linux) 或.\venv\Scripts\python app.py(Windows)。5.2 数据库操作失败或表不存在问题添加或查询数据时报错sqlalchemy.exc.OperationalError: no such table: todo。原因数据库表没有创建成功。解决确保db.create_all()在with app.app_context():块内被正确执行。可以手动初始化打开Python交互环境在项目根目录激活虚拟环境后输入python然后执行 from app import app, db with app.app_context(): ... db.create_all()如果模型类class Todo定义有修改比如新增了字段db.create_all()不会自动更新已存在的表。你需要使用数据库迁移工具如Flask-Migrate或者在开发初期删除旧的instance/todo.db文件重新运行创建命令。5.3 端口被占用问题启动时报错OSError: [Errno 48] Address already in use。原因默认的5000端口已经被其他程序可能是你之前未正确退出的Flask进程占用。解决按CtrlC终止当前服务器重新运行flask run。如果还不行可以指定另一个端口运行flask run --port 5001然后访问http://127.0.0.1:5001。在Mac/Linux上可以用lsof -i :5000查找占用端口的进程ID然后用kill -9 进程ID强制结束它。在Windows上可以用netstat -ano | findstr :5000找到PID然后在任务管理器中结束对应进程。5.4 修改代码后页面无变化问题修改了HTML模板或Python代码刷新浏览器没效果。原因未开启调试模式服务器没有自动重启。浏览器缓存浏览器缓存了旧的CSS、JS或HTML文件。解决确认已按4.2节开启调试模式并观察命令行是否有* Detected change in ...的重载提示。对于静态文件CSS/JS在浏览器中按CtrlF5或CmdShiftR进行强制刷新。对于模板调试模式开启后Flask默认会每次请求都重新加载模板所以通常刷新页面即可。如果不行检查模板文件名和路径是否正确。5.5 Flash消息不显示问题操作后设置了flash()消息但页面上看不到。排查检查模板确保在base.html或相应模板中有遍历和显示get_flashed_messages()的代码块。检查消息类别在模板中我们根据类别设置了不同的Bootstrap CSS类alert-{{ category }}。如果你在flash()时使用了‘error‘模板里将其映射为了‘danger‘。确保类别匹配或者模板逻辑能处理你使用的类别。检查重定向flash消息只在下一次请求时显示。确保你在flash()之后进行了重定向redirect而不是直接渲染模板。如果直接render_template消息会被设置但立即在同一个请求中被消耗掉页面刷新后才会出现这不符合用户预期。5.6 表单提交后出现405 Method Not Allowed错误问题点击添加按钮页面显示 “Method Not Allowed”。原因视图函数的路由没有允许POST方法。例如处理添加的视图函数add_todo的路由装饰器是app.route(‘/add‘)默认只接受GET请求。解决确保表单提交的目标URL对应的视图函数其路由装饰器包含了methods[‘POST‘]即app.route(‘/add‘, methods[‘POST‘])。6. 项目扩展与下一步学习方向这个简易的Todo List已经实现了核心的Web功能。如果你想继续深入这里有几个明确的扩展方向每个方向都能带你学习新的知识点6.1 前端体验优化引入JavaScript现在的完成和删除操作需要刷新整个页面。可以用一点JavaScript比如Fetch API改成异步操作体验会流畅很多。给按钮添加事件监听移除表单和链接改为给复选框和删除按钮绑定onclick事件。发送异步请求在事件处理函数中使用fetch()向对应的后端API需要新建只返回JSON的视图函数发送PUT或DELETE请求。局部更新DOM收到成功响应后用JavaScript直接更新页面上的列表项状态或移除该元素无需刷新页面。 这一步会让你初步接触前后端分离的思维。6.2 用户系统Flask-Login一个真正的应用通常需要用户登录。可以使用Flask-Login扩展。pip install flask-login创建一个User模型包含id、username、password_hash存储加密后的密码切勿明文存储。使用flask_login.LoginManager来管理用户会话。为某些视图添加login_required装饰器保护它们只允许登录用户访问。添加注册、登录、注销的页面和视图函数。 这会让你理解会话Session、认证Authentication和授权Authorization的基本概念。6.3 部署到云服务器实战演练让本地应用在公网可访问。关闭调试模式这是安全第一步。更换密钥生成一个复杂的随机字符串作为SECRET_KEY。选择部署方式传统服务器购买一台云服务器如腾讯云轻量应用服务器安装Python、Nginx、Gunicorn。用Gunicorn作为WSGI服务器运行Flask应用用Nginx作为反向代理处理静态文件和负载均衡。这是最经典、理解最深入的方式。容器化学习Docker将你的应用和所有依赖打包成一个镜像。然后可以在任何支持Docker的环境包括云厂商的容器服务中一键运行。这是现代应用部署的主流趋势。平台即服务使用像Heroku、VercelPython支持有限、或国内一些云厂商的PaaS产品。它们抽象了服务器管理你只需要提交代码。适合快速原型验证。 部署是整个开发流程的最后一步也是检验项目完整性的试金石会遇到环境配置、网络、安全等一系列新问题。这个简易的Flask Web程序项目就像你学习编程时写的第一个“Hello, World!”。它简单但完整。通过亲手实现它你不仅学会了Flask的基本用法更重要的是你看到了一个Web请求从浏览器发出到服务器处理再到数据库交互最后返回响应的完整闭环。理解了这条路再去学习更复杂的框架、更高级的功能你心里就有了一张清晰的地图。

相关新闻

最新新闻

日新闻

周新闻

月新闻