当前位置: 首页 > news >正文

081、Flask 入门:路由、模板、请求响应——一个博客的从零搭建

081、Flask 入门:路由、模板、请求响应——一个博客的从零搭建

一个让我半夜爬起来改代码的Bug

上周帮朋友调试一个Flask博客项目,页面死活返回404。我盯着路由文件看了半小时,最后发现他写的是@app.route(‘/post/’+str(id))——这种拼接路由的方式在Flask里根本行不通。Flask的路由是静态匹配的,动态参数必须用<int:id>这种尖括号语法。这个坑我当年也踩过,今天就从这里开始,带你把Flask博客从零搭起来。

路由:别把URL当字符串拼接

Flask的路由装饰器@app.route()接受的是URL模式,不是字符串模板。正确的动态路由写法:

fromflaskimportFlask app=Flask(__name__)# 这里踩过坑:别写成 @app.route('/post/'+id)@app.route('/post/<int:post_id>')defshow_post(post_id):returnf'文章ID:{post_id}'

<int:post_id>表示这个段必须是整数,Flask会自动做类型转换。如果你想要字符串,用<string:slug>或者直接<slug>(默认就是字符串)。还有<path:subpath>可以匹配包含斜杠的路径,比如做分类嵌套时很有用。

路由的HTTP方法默认只响应GET。写博客系统时,处理表单提交必须显式声明:

@app.route('/create',methods=['GET','POST'])defcreate_post():ifrequest.method=='POST':# 处理表单数据return'文章已创建'returnrender_template('create.html')

别这样写:@app.route('/create', methods=['POST'])然后单独写一个GET路由——Flask允许一个函数处理多个方法,代码更干净。

模板:Jinja2的坑与技巧

Flask默认用Jinja2模板引擎。模板文件放在templates/目录下,这是硬性规定,别自作聪明改路径。

模板里最常用的就是变量替换和控制结构:

<!-- templates/post.html --><h1>{{ post.title }}</h1><p>{{ post.content }}</p>{% if post.tags %}<ul>{% for tag in post.tags %}<li>{{ tag }}</li>{% endfor %}</ul>{% endif %}

这里有个坑:Jinja2的{{ }}会自动转义HTML。如果你存的是富文本内容,需要用|safe过滤器:

<div>{{ post.body_html | safe }}</div>

但别滥用safe——用户输入的内容直接safe等于给XSS攻击开门。正确的做法是用Markdown解析后再safe,或者用bleach库过滤。

模板继承是博客系统的骨架。写一个base.html

<!DOCTYPEhtml><html><head><title>{% block title %}我的博客{% endblock %}</title></head><body><nav><ahref="/">首页</a><ahref="/create">写文章</a></nav><main>{% block content %}{% endblock %}</main></body></html>

子模板只需要填充block

{% extends "base.html" %} {% block title %}{{ post.title }} - 我的博客{% endblock %} {% block content %}<article><h1>{{ post.title }}</h1><div>{{ post.body_html | safe }}</div></article>{% endblock %}

请求与响应:别把数据搞丢了

Flask的request对象封装了所有HTTP请求数据。处理表单时,最常用的就是request.form

fromflaskimportrequest,redirect,url_for@app.route('/create',methods=['POST'])defcreate_post():title=request.form.get('title','').strip()content=request.form.get('content','').strip()# 这里踩过坑:别用 request.form['title'],键不存在会抛KeyErrorifnottitle:return'标题不能为空',400# 保存到数据库...returnredirect(url_for('show_post',post_id=new_id))

url_for()是路由的逆向解析——根据函数名生成URL。好处是路由路径改了,代码不用改。别硬编码URL,比如redirect('/post/1'),哪天路由改成/article/1你就得满世界找。

文件上传用request.files

@app.route('/upload',methods=['POST'])defupload_image():file=request.files.get('image')iffileandfile.filename:# 别这样写:直接保存用户文件名# 安全做法:生成随机文件名importuuid ext=file.filename.rsplit('.',1)[1].lower()filename=f"{uuid.uuid4().hex}.{ext}"file.save(f'static/uploads/{filename}')returnurl_for('static',filename=f'uploads/{filename}')return'没有文件',400

响应除了返回字符串,还可以返回元组(响应体, 状态码, 头部字典)。做API时常用:

@app.route('/api/post/<int:post_id>')defapi_post(post_id):post=get_post(post_id)ifpostisNone:return{'error':'文章不存在'},404return{'id':post.id,'title':post.title,'content':post.content}

Flask 2.0+支持直接返回字典,会自动转JSON。老版本需要jsonify()

实战:一个极简博客的核心逻辑

把上面这些拼起来,一个博客的CRUD雏形就有了:

fromflaskimportFlask,render_template,request,redirect,url_for app=Flask(__name__)# 假装这是数据库posts=[]counter=0@app.route('/')defindex():returnrender_template('index.html',posts=posts)@app.route('/post/<int:post_id>')defshow_post(post_id):post=next((pforpinpostsifp['id']==post_id),None)ifpostisNone:return'文章不存在',404returnrender_template('post.html',post=post)@app.route('/create',methods=['GET','POST'])defcreate_post():globalcounterifrequest.method=='POST':title=request.form.get('title','').strip()content=request.form.get('content','').strip()ifnottitle:return'标题不能为空',400counter+=1posts.append({'id':counter,'title':title,'content':content})returnredirect(url_for('show_post',post_id=counter))returnrender_template('create.html')if__name__=='__main__':app.run(debug=True)

模板文件templates/index.html

{% extends "base.html" %} {% block content %}<h1>最新文章</h1>{% for post in posts %}<article><h2><ahref="{{ url_for('show_post', post_id=post.id) }}">{{ post.title }}</a></h2></article>{% else %}<p>还没有文章,<ahref="{{ url_for('create_post') }}">写一篇</a></p>{% endfor %} {% endblock %}

个人经验建议

  1. 调试模式一定要开app.run(debug=True)。改代码自动重启,报错直接显示在浏览器里。生产环境记得关掉,不然用户能看到你的源码。

  2. 路由别用正则:Flask的路由语法已经够用了。非要正则的话,用@app.route('/post/<regex("[a-z]+"):slug>'),但维护起来很痛苦。

  3. 模板里少写逻辑:复杂的计算、数据库查询放在视图函数里,模板只负责展示。Jinja2不是编程语言,别为难它。

  4. 404页面要优雅:注册一个404处理器:

@app.errorhandler(404)defnot_found(e):returnrender_template('404.html'),404
  1. 别用全局列表当数据库:上面的例子只是为了演示。真实项目用SQLite+Flask-SQLAlchemy,或者直接上PostgreSQL。

Flask的哲学是“微框架”——给你最核心的东西,剩下的你自己选。这种自由既是优点也是陷阱。刚开始学,先按我说的套路来,等熟悉了再折腾扩展。下一篇我会讲Flask-SQLAlchemy怎么集成,把博客的数据持久化做起来。

http://www.jsqmd.com/news/1089189/

相关文章:

  • N_m3u8DL-RE:跨平台流媒体下载工具的全面解析与实践指南
  • 深度解析开源项目:MCQTSS_QQMusic如何高效实现QQ音乐资源解析与下载
  • 一份现代知识系统的全景地图
  • 51单片机与TCS3200:从脉冲计数到RGB值的实战解析
  • Mac上Navicat Premium 12的安装、激活与核心功能上手
  • 四层板铜厚选型系统化校验流程
  • AI 交互体验设计:从响应延迟到信任构建的体验工程实践
  • RimSort模组管理3步法:从混乱到有序,让RimWorld模组不再冲突
  • Postman自动化测试中401权限问题的系统化解决方案
  • torch.hub.load()实战指南:从云端拉取到本地部署的完整路径
  • 【ISO15031_OBD诊断】-0.2-时序参数P2CAN与P2*CAN深度解析
  • 解锁AMD Ryzen潜能的免费终极指南:SMUDebugTool硬件调优完整教程
  • Anaconda一站式部署指南:从零安装到Navigator稳定运行
  • 从工厂订货系统看数据流图:一个典型应用场景的深度剖析
  • 从真题难度变迁看考研数学二备考策略:2015-2022年深度解析
  • AMD Ryzen调试工具SMUDebugTool:免费开源硬件调优终极指南
  • 抖音批量下载助手:高效获取用户主页视频的终极解决方案
  • RimSort:拯救你的RimWorld模组管理噩梦,让游戏加载从未如此顺畅
  • 深入剖析Multi-Cycle约束:从基础语法到跨时钟域实战
  • Apache Shiro反序列化漏洞深度解析:从原理到实战代码审计
  • AI论文写作工具的合规指南:从文献整理到成稿的合规流程解析?
  • Windows终端进阶:打造无缝集成的Vim工作流
  • ROS智能小车进阶:基于YOLOv3与网络摄像头的动态目标追踪实战
  • 从Confluence到信创知识库:国产化替代的迁移路径和避坑指南
  • SMUDebugTool:AMD Ryzen处理器底层调试与超频的终极专业工具
  • WarcraftHelper:魔兽争霸3性能优化终极指南,让经典游戏焕发新生
  • QGIS 3.34尝鲜3DTiles:从惊艳官宣到实战踩坑全记录
  • QQ音乐解密终极指南:3分钟掌握qmcdump转换技巧
  • 从原理到实战:基于TOTP算法的动态口令生成与Google身份验证器集成指南
  • 三分钟免费解锁Wand游戏修改器完整专业版:终极本地增强指南