标头抽进 base,三个页面各 extend 一次,改一次导航全站跟着变。 登录页和注册页可以共用同一张模板,差别只在传进去的 title。 验收标准是一串状态码:进不去、登入、进得去、登出、又进不去。
这一篇对应教材 6.2 与 6.4 到 6.6:建立通用标头、修改登入页面的端点与模板、 确认注册与登录页面的运作情况。 上一篇把注册页改利索了,这一篇收尾:标头、登录页、整体验收。
一、标头里放什么
<header>
<h1><a href="{{ url_for('main.index') }}">物件侦测</a></h1>
<nav>
{% if current_user.is_authenticated %}
<a href="{{ url_for('images.index') }}">图片</a>
<span>你好,{{ current_user.name }}</span>
{% else %}
<a href="{{ url_for('auth.login') }}">登入</a>
{% endif %}
</nav>
</header>
四样东西:站点名、主导航、登录状态、退出入口。 它们有一个共同点——每一页都要显示。所以放 base,不放子模板。 判断标准还是那一句:不是「长得一样」,是「改一处要生效到所有页面」。
二、抽出来到底省了什么
实测未登入时,登录页与注册页渲出来的 header 完全相同——因为两份 extend 的是同一个 base。 不抽的话,每个页面各写一份 nav,加一个菜单项要改五处,漏一处就不一致。 这类不一致最难被发现:谁也不会有事没事把五个页面的导航挨个比一遍。
标头里那个 current_user 不用传,flask-login 注册了上下文处理器。
三、登录页和注册页共用一张模板
两页的结构完全一样:标题、表单、错误提示。差别只有 title 和字段数量。 实测登录页渲出 4 个 input,注册页 6 个——差的那两个是 name 和 confirm。 字段数量由表单类决定,模板只负责「每个字段摆一行」。所以一张模板能伺候两个页面。
教材分两节写这两张模板(6.3 与 6.5),实际做法可以是同一份文件加参数。 分不分都行,关键是别把字段名写进模板。
四、验收:一串状态码
实测五步:未登入访问首页 302 到登录页;登入 302 回首页;再访问首页 200;登出 302;再访问又 302。 这一串就是「验证功能能用」的验收标准。 它比「打开页面看看长得对不对」可靠得多,因为它可以写进自动化测试。 注意登出那一步的 Location 是 /auth/login,不是 /——登出之后跳登录页,比跳首页更符合预期。
这些跳转目标全由端点里那句 redirect(url_for(...)) 决定,改一行就能改行为。
五、这一章补的是什么
| 补的东西 | 解决什么 |
|---|---|
| 确认密码字段 | 打错字也能注册成功的尴尬 |
| 字段循环渲染 | 加字段不用改模板 |
| 通用标头 | 改一次导航全站生效 |
| 令牌处理 | 测试客户端也能过 CSRF |
这四项都不产生新功能。它们加在一起,是「能跑的 demo」和「像样的应用」之间的差别。 下一步开始才是真功能:上传图片、跑物件侦测、把结果存下来。 前面六章攒的工具,从下一篇起开始真用。
六、动手 Demo:通用标头与登录页
这个 Demo 先把标头抽进 base,验证三个页面的 header 是同一份,再用一串状态码把整个验证流程走一遍。 第四节那五步是这一章的验收清单。
<nav>
{% if current_user.is_authenticated %}
<a href="{{ url_for('images.index') }}">图片</a>
<span>你好,{{ current_user.name }}</span>
<form method="post" action="{{ url_for('auth.logout') }}">
<button>登出</button></form>
{% else %}
<a href="{{ url_for('auth.login') }}">登入</a>
{% endif %}
</nav>
实跑输出:
--------------------------------------------------------------------
通用标头与登录页
--------------------------------------------------------------------
① 标头里放什么
--------------------------------------------------------------------
[代码原样]
| <header>
| <h1><a href="{{ url_for('main.index') }}">物件侦测</a></h1>
| <nav>
| {% if current_user.is_authenticated %}
| <a href="{{ url_for('images.index') }}">图片</a>
| <span>你好,{{ current_user.name }}</span>
| ...
| {% else %}
| <a href="{{ url_for('auth.login') }}">登入</a>
| {% endif %}
| </nav>
| </header>
三个页面各 include 一次,改一次全站生效。
! 标头里通常四样:站点名、主导航、登录状态、退出入口。判断标准还是「改一处要生效到所有页面」。
② 三个页面的标头是不是同一份
--------------------------------------------------------------------
未登入时,登录页与注册页的标头相同 -> True
内容 -> <header> <h1><a href="/">物件侦测</a></h1> <nav> <a href="/auth/login">登入</a> <a
! 两份一模一样,因为它们 extend 的是同一个 base。这就是抽标头的收益:改一次导航,全站跟着变。
③ 登录页和注册页共用一张模板
--------------------------------------------------------------------
两张模板都是「遍历 form 里每个字段」那一套,只是传进去的 title 不同。
登录页渲出 4 个 input,注册页渲出 6 个
! 字段数量由表单类决定,模板只负责「每个字段摆一行」。
所以这两页可以共用一张模板,这也解释了为什么加字段时模板一个字不用改。
④ 走一遍完整流程
--------------------------------------------------------------------
1) 未登入访问首页 -> 302,Location=/auth/login?next=%2F
2) 登入 -> 302,Location=/
3) 再访问首页 -> 200
已登入时的标头 -> <header> <h1><a href="/">物件侦测</a></h1> <nav> <a href="/images/">图片</a> <span
4) 登出 -> 302,Location=/auth/login
5) 登出后再访问首页 -> 302
! 五步全对得上:进不去、登入、进得去、登出、又进不去。
这一串就是「验证功能能用」的验收标准,比看页面好看多了。
⑤ 这一章补的是什么
--------------------------------------------------------------------
| 补的东西 | 解决什么 |
|---|---|
| 确认密码字段 | 打错字也能注册成功的尴尬 |
| 字段循环渲染 | 加字段不用改模板 |
| 通用标头 | 改一次导航全站生效 |
| 令牌处理 | 测试客户端也能过 CSRF |
第 6 章到此结束。这几项都不产生新功能,但它们是「像样的应用」和「能跑的 demo」之间的差别。
第二节那两行 header 一模一样,是「抽标头」最直接的证据。 第三节的 4 与 6,差的就是 name 与 confirm 两个字段。 第四节五步串起来看:302、302、200、302、302——每个数字都有意义。
30 秒实验
- 给导航加一个「关于」链接,只改 base,然后逐个打开三个页面看是不是都有了。
- 把登录页复制成独立模板,只改标题,看两份以后会不会走样。
- 想一想:为什么登出之后跳登录页比跳首页更合适?
常见误区
- 每个页面各写一份导航:加菜单项要改五处,漏一处就不一致。
- 模板里写死地址:应该用 url_for,写死的话换前缀就全断。
- 登出之后跳首页:用户看到首页还以为没登出成功。
- 验收只看页面好不好看:状态码那五步才是能自动化的部分。
自测三问(点开对答案)
1. 标头为什么放 base? —— 每一页都要显示,放一次改一次全站生效。 2. 登录页和注册页为什么能共用模板? —— 字段数量由表单类决定,模板只负责每个字段摆一行。 3. 「验证功能能用」的验收怎么走? —— 看五步状态码:进不去、登入、进得去、登出、又进不去。第 6 章到此结束。下一篇做真功能:图片上传,文件怎么收、怎么存、怎么防。
- ☐ (本篇完,下一篇 7.1 上传目的地与图片显示端点)