Flask-Login使用示例

article/2025/8/19 4:02:21

项目结构

首先创建以下文件结构:

flask_login_use/
├── app.py
├── models.py
├── requirements.txt
└── templates/├── base.html├── index.html├── login.html├── register.html└── profile.html

1. requirements.txt

Flask==2.3.3
Flask-Login==0.6.3
Flask-WTF==1.1.1
WTForms==3.0.1
Werkzeug==2.3.7
email_validator

2. models.py - 用户模型

from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hashclass User(UserMixin):def __init__(self, id, username, email, password_hash):self.id = idself.username = usernameself.email = emailself.password_hash = password_hashdef check_password(self, password):return check_password_hash(self.password_hash, password)@staticmethoddef create_password_hash(password):return generate_password_hash(password)# 模拟数据库存储
users_db = {}
next_user_id = 1def get_user(user_id):return users_db.get(int(user_id))def get_user_by_username(username):for user in users_db.values():if user.username == username:return userreturn Nonedef create_user(username, email, password):global next_user_idpassword_hash = User.create_password_hash(password)user = User(next_user_id, username, email, password_hash)users_db[next_user_id] = usernext_user_id += 1return user

3. app.py - 主应用文件

from flask import Flask, render_template, request, redirect, url_for, flash
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo
from models import User, get_user, get_user_by_username, create_userapp = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'  # 在生产环境中使用更安全的密钥# 初始化Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message = '请先登录以访问此页面。'
login_manager.login_message_category = 'info'@login_manager.user_loader
def load_user(user_id):return get_user(user_id)# 表单类
class LoginForm(FlaskForm):username = StringField('用户名', validators=[DataRequired()])password = PasswordField('密码', validators=[DataRequired()])submit = SubmitField('登录')class RegisterForm(FlaskForm):username = StringField('用户名', validators=[DataRequired(), Length(min=4, max=20, message='用户名长度必须在4-20个字符之间')])email = StringField('邮箱', validators=[DataRequired(), Email(message='请输入有效的邮箱地址')])password = PasswordField('密码', validators=[DataRequired(), Length(min=6, message='密码长度至少6个字符')])password2 = PasswordField('确认密码', validators=[DataRequired(), EqualTo('password', message='两次输入的密码不一致')])submit = SubmitField('注册')# 路由
@app.route('/')
def index():return render_template('index.html')@app.route('/login', methods=['GET', 'POST'])
def login():if current_user.is_authenticated:return redirect(url_for('index'))form = LoginForm()if form.validate_on_submit():user = get_user_by_username(form.username.data)if user and user.check_password(form.password.data):login_user(user)flash('登录成功!', 'success')# 获取用户尝试访问的页面next_page = request.args.get('next')return redirect(next_page) if next_page else redirect(url_for('index'))else:flash('用户名或密码错误', 'danger')return render_template('login.html', form=form)@app.route('/register', methods=['GET', 'POST'])
def register():if current_user.is_authenticated:return redirect(url_for('index'))form = RegisterForm()if form.validate_on_submit():# 检查用户名是否已存在if get_user_by_username(form.username.data):flash('用户名已存在', 'danger')else:# 创建新用户user = create_user(username=form.username.data,email=form.email.data,password=form.password.data)flash('注册成功!请登录。', 'success')return redirect(url_for('login'))return render_template('register.html', form=form)@app.route('/logout')
@login_required
def logout():logout_user()flash('您已成功登出', 'info')return redirect(url_for('index'))@app.route('/profile')
@login_required
def profile():return render_template('profile.html')@app.route('/protected')
@login_required
def protected():return f'<h1>受保护的页面</h1><p>你好,{current_user.username}!这是一个需要登录才能访问的页面。</p><a href="{url_for("index")}">返回首页</a>'if __name__ == '__main__':app.run(debug=True)

4. 模板文件

base.html - 基础模板

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{% block title %}Flask-Login 示例{% endblock %}</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body><nav class="navbar navbar-expand-lg navbar-dark bg-dark"><div class="container"><a class="navbar-brand" href="{{ url_for('index') }}">Flask-Login 示例</a><div class="navbar-nav ms-auto">{% if current_user.is_authenticated %}<a class="nav-link" href="{{ url_for('profile') }}">个人资料</a><a class="nav-link" href="{{ url_for('protected') }}">受保护页面</a><a class="nav-link" href="{{ url_for('logout') }}">登出 ({{ current_user.username }})</a>{% else %}<a class="nav-link" href="{{ url_for('login') }}">登录</a><a class="nav-link" href="{{ url_for('register') }}">注册</a>{% endif %}</div></div></nav><div class="container mt-4">{% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}{% for category, message in messages %}<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show" role="alert">{{ message }}<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>{% endfor %}{% endif %}{% endwith %}{% block content %}{% endblock %}</div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

index.html - 首页

{% extends "base.html" %}{% block content %}
<div class="row"><div class="col-md-8 mx-auto"><div class="jumbotron bg-light p-5 rounded"><h1 class="display-4">欢迎使用 Flask-Login 示例</h1><p class="lead">这是一个演示 Flask-Login 功能的完整示例应用。</p>{% if current_user.is_authenticated %}<h4>你好,{{ current_user.username }}!</h4><p>你已经成功登录。你可以:</p><ul><li><a href="{{ url_for('profile') }}">查看个人资料</a></li><li><a href="{{ url_for('protected') }}">访问受保护的页面</a></li><li><a href="{{ url_for('logout') }}">登出</a></li></ul>{% else %}<p>请先登录或注册以体验完整功能。</p><a class="btn btn-primary btn-lg" href="{{ url_for('login') }}" role="button">登录</a><a class="btn btn-secondary btn-lg" href="{{ url_for('register') }}" role="button">注册</a>{% endif %}</div></div>
</div>
{% endblock %}

login.html - 登录页面

{% extends "base.html" %}{% block title %}登录 - Flask-Login 示例{% endblock %}{% block content %}
<div class="row"><div class="col-md-6 mx-auto"><div class="card"><div class="card-header"><h3>登录</h3></div><div class="card-body"><form method="POST">{{ form.hidden_tag() }}<div class="mb-3">{{ form.username.label(class="form-label") }}{{ form.username(class="form-control") }}{% if form.username.errors %}<div class="text-danger">{% for error in form.username.errors %}<small>{{ error }}</small>{% endfor %}</div>{% endif %}</div><div class="mb-3">{{ form.password.label(class="form-label") }}{{ form.password(class="form-control") }}{% if form.password.errors %}<div class="text-danger">{% for error in form.password.errors %}<small>{{ error }}</small>{% endfor %}</div>{% endif %}</div><div class="d-grid">{{ form.submit(class="btn btn-primary") }}</div></form><div class="text-center mt-3"><p>还没有账户? <a href="{{ url_for('register') }}">立即注册</a></p></div></div></div></div>
</div>
{% endblock %}

register.html - 注册页面

{% extends "base.html" %}{% block title %}注册 - Flask-Login 示例{% endblock %}{% block content %}
<div class="row"><div class="col-md-6 mx-auto"><div class="card"><div class="card-header"><h3>注册</h3></div><div class="card-body"><form method="POST">{{ form.hidden_tag() }}<div class="mb-3">{{ form.username.label(class="form-label") }}{{ form.username(class="form-control") }}{% if form.username.errors %}<div class="text-danger">{% for error in form.username.errors %}<small>{{ error }}</small>{% endfor %}</div>{% endif %}</div><div class="mb-3">{{ form.email.label(class="form-label") }}{{ form.email(class="form-control") }}{% if form.email.errors %}<div class="text-danger">{% for error in form.email.errors %}<small>{{ error }}</small>{% endfor %}</div>{% endif %}</div><div class="mb-3">{{ form.password.label(class="form-label") }}{{ form.password(class="form-control") }}{% if form.password.errors %}<div class="text-danger">{% for error in form.password.errors %}<small>{{ error }}</small>{% endfor %}</div>{% endif %}</div><div class="mb-3">{{ form.password2.label(class="form-label") }}{{ form.password2(class="form-control") }}{% if form.password2.errors %}<div class="text-danger">{% for error in form.password2.errors %}<small>{{ error }}</small>{% endfor %}</div>{% endif %}</div><div class="d-grid">{{ form.submit(class="btn btn-success") }}</div></form><div class="text-center mt-3"><p>已有账户? <a href="{{ url_for('login') }}">立即登录</a></p></div></div></div></div>
</div>
{% endblock %}

profile.html - 个人资料页面

{% extends "base.html" %}{% block title %}个人资料 - Flask-Login 示例{% endblock %}{% block content %}
<div class="row"><div class="col-md-8 mx-auto"><div class="card"><div class="card-header"><h3>个人资料</h3></div><div class="card-body"><table class="table"><tr><th>用户ID:</th><td>{{ current_user.id }}</td></tr><tr><th>用户名:</th><td>{{ current_user.username }}</td></tr><tr><th>邮箱:</th><td>{{ current_user.email }}</td></tr><tr><th>登录状态:</th><td>{% if current_user.is_authenticated %}<span class="badge bg-success">已登录</span>{% else %}<span class="badge bg-danger">未登录</span>{% endif %}</td></tr></table><div class="mt-3"><a href="{{ url_for('index') }}" class="btn btn-primary">返回首页</a><a href="{{ url_for('logout') }}" class="btn btn-danger">登出</a></div></div></div></div>
</div>
{% endblock %}

运行应用

  1. 安装依赖:
pip install -r requirements.txt
  1. 运行应用:
python app.py
  1. 在浏览器中访问 http://localhost:5000

或者用uv来控制项目:
在这里插入图片描述

在这里插入图片描述

页面测试:
在这里插入图片描述
❤️➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖❤️

在这里插入图片描述

❤️➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖❤️
在这里插入图片描述
❤️➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖❤️

在这里插入图片描述
❤️➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖❤️

在这里插入图片描述
❤️➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖❤️

在这里插入图片描述


http://www.hkcw.cn/article/fVKZuArZth.shtml

相关文章

刘浩存和王安宇借位吻戏 引发观众热议

在热播剧《陷入我们的热恋》中,刘浩存和王安宇饰演的少年恋人徐栀和陈路周在灯泡的温柔光晕下,脸庞越靠越近,呼吸似乎交融在一起。然而,镜头巧妙切换到墙上的影子,只留下观众对着空画面脑补那个未完成的吻。这一幕再次引发了关于吻戏借位的讨论。这并不是刘浩存第一次因吻…

步行者晋级总决赛 东决MVP西亚卡姆闪耀

北京时间6月1日,NBA东部决赛G6在印第安纳步行者主场进行,对阵纽约尼克斯。上半场双方比分紧咬,半场结束时步行者以58-54领先。第三节步行者打出34-23的攻势,确立了15分的领先优势进入末节。末节比赛中,尼克斯的唐斯带领球队反扑,但步行者哈利伯顿保持两位数的领先优势。最…

小米汽车5月交付量超过2.8万台 大规模量产在即

6月1日消息,@小米汽车称,2025年5月小米汽车交付量超过28000台。同时,小米汽车正在为小米YU7大规模量产做准备。责任编辑:zx0176

被游戏沉迷与充值退费困扰的童年 留守儿童的手机依赖症

陕西榆林清涧县的帅帅今年10岁,和14岁的姐姐跟随爷爷奶奶生活多年,父母则在外地工地上打工。今年清明假期回家时,父母偶然听奶奶提到,儿子用爷爷的手机打游戏并充值了数千元。一家人对此非常生气,父亲还因此打了帅帅几顿。河南周口郸城县的小丘和弟弟也留守在农村老家,父…

因滞销和收购价低,瓜农把西瓜扔鱼塘喂鱼 市场饱和致价格骤降

5月29日,广西扶绥县的瓜农因西瓜滞销和收购价低而将大量西瓜扔进池塘或任其烂在地里。山圩镇一名瓜农表示,今年西瓜收购价只有每斤两毛钱,远低于往年水平,他预计会亏损四五万元,地里有10万斤西瓜被丢弃。另一位瓜农黄先生说,种植一亩西瓜需要投入约1500元,但卖出后只能收…

德甲萨尔布吕肯官宣樊振东加盟 迎接新挑战

德甲联赛萨尔布吕肯乒乓球甲级俱乐部宣布,奥运冠军樊振东加盟。樊振东表示,他非常期待在萨尔布吕肯和德甲的新挑战,体验新的环境,并与球队一起赢得更多胜利。莫雷加德也表示,能和樊振东成为队友感到很荣幸。责任编辑:zx0176

4.2.5 Spark SQL 分区自动推断

在本节实战中&#xff0c;我们学习了Spark SQL的分区自动推断功能&#xff0c;这是一种提升查询性能的有效手段。通过创建具有不同分区的目录结构&#xff0c;并在这些目录中放置JSON文件&#xff0c;我们模拟了一个分区表的环境。使用Spark SQL读取这些数据时&#xff0c;Spar…

spring事务的面试题 —— 事务的特性、传播机制、隔离机制、注解

目录 1、事务的四个特性 2、如何使用事务Transactional 3、事务的传播机制 3.1 事务传播机制的概念 3.2 事务传播机制的分类 1、PROPAGATION_REQUIRED &#xff08;默认&#xff09; 2、PROPAGATION_SUPPORTS 3、PROPAGATION_MANDATORY 4、PROPAGATION_REQUIRES_NEW 5…

以色列将阻止中东国家外长代表团访问约旦河西岸 会议面临取消

当地时间5月31日,一位以色列官员表示,以色列不会允许原定于在约旦河西岸城市拉姆安拉举行的阿拉伯外交部长会议召开。同一天,巴勒斯坦民族权力机构官员表示,关于周日的会议是否能够举行的问题仍在讨论中。据《以色列时报》报道,一名以色列高级官员证实,以方决定阻止中东国…

克瓦拉茨赫利亚:加盟巴黎是正确的选择,梦想成真

北京时间6月1日,巴黎圣日耳曼夺得了队史首座欧冠冠军。赛后,克瓦拉茨赫利亚接受了TNT体育的采访。他表达了难以置信的情绪:“这太疯狂了。我无法想象今天我成为了欧洲冠军联赛的冠军。梦想成真,这种情绪简直无法形容。”克瓦拉茨赫利亚自豪地披着格鲁吉亚国旗,他表示:“我…

特朗普为何支持日铁收购美国钢铁 政治博弈下的变脸艺术

这年头的政治戏码堪比好莱坞大片。还记得去年底特朗普在社交媒体上的惊天咆哮吗?“美国钢铁必须姓美!”这位曾经高喊“美国优先”的民族主义者,转眼间变成了跨国资本的代言人。五个月内,特朗普从强烈反对日本制铁收购美国钢铁公司,到大力支持并声称这项交易能创造7万个岗位…

网友喊话佛山龙舟队帮帮辽宁队 房东队的传承与拼搏

去年端午节,广东佛山沙步龙船队因队员黎国添的发言在网络上走红,被网友戏称为“房东队”。今年5月27日,九派新闻现场观看了沙步龙船队的传统仪式“拜大廟”,并采访了黎国添。他不仅是沙步经济社社长,还是十几套房的房东以及一家光伏发电公司的老板。黎国添表示,虽然队伍中…

专家谈中国向韩国开放稀土供应 博弈策略显现

美国一直关注的稀土资源被中国严格管控,这让美国感到十分困扰。然而,中国却向韩国开放了部分稀土供应,这是中美博弈中的一步棋。此前有猜测认为,中国为了换取美国降低关税可能会放松稀土出口。但到了5月中旬,中国对稀土出口审批流程变得更加严格,主要针对的是美国。由于无…

加沙近1岁孩子只有3.9公斤 饥荒危机加剧

以色列今年3月重新封锁加沙地带并恢复军事行动,导致当地民众严重缺乏基本生活物资,面临饥荒。加沙母亲扎伊塔尔表示,她经常领不到物资援助,11个月大的儿子体重只有3.9公斤,不到正常孩子的三分之一,已被诊断为严重营养不良。尽管20天前他们获得了医疗撤离许可,但她仍在等…

东风日产N7累计大定17215台 黑马车型强势崛起

东风日产官方发布海报显示,日产N7累计大定已达17215台,成为国内中大型轿车的黑马车型。该车于4月27日上市,共推出5款车型,售价区间为11.99-14.99万元。新车车长4930mm,轴距2915mm,是一款中大型纯电轿车,车身尺寸配置与比亚迪汉相似。起售价仅为11.99万元,顶配不到15万,…

今起试行!中国单方面免签“朋友圈”再增5国 拉美国家首次纳入

今天起,中国对巴西、阿根廷、智利、秘鲁、乌拉圭五国持普通护照人员试行免签政策,中国单方面免签“朋友圈”再添新成员。2025年6月1日至2026年5月31日期间,这五个国家的公民来华经商、旅游观光、探亲访友、交流访问或过境不超过30天时,可免办签证入境。这是中方首次将免签政…

纳赛尔:把欧冠冠军献给故去的母亲,梦想成真夜

北京时间6月1日凌晨,2024-2025赛季欧冠决赛在慕尼黑举行,巴黎圣日耳曼以5-0大胜国际米兰,赢得了队史首座欧冠冠军。赛后,巴黎主席纳赛尔在混采区接受了采访。他表示,当晚他要和欧冠奖杯一起睡,这是一个梦想,他感到非常自豪。他还表示要把这场胜利献给已经去世的母亲,她…

小红书 发评论 分析 x-s x-t

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向过程 部分Python代码 ck jso…

Python打卡训练营Day41

DAY 41 简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化&#xff1a;调整一个批次的分布&#xff0c;常用与图像数据特征图&#xff1a;只有卷积操作输出的才叫特征图调度器&#xff1a;直接修改基础学习率 卷积操作常见流程如下&#xff1a; 1. 输入 → 卷积层 →…

樊振东为何加盟德国乒乓球俱乐部 欧洲冒险新挑战

樊振东在社交媒体上发布了一张观看欧冠比赛的照片,引发了球迷对他欧洲之行目的的猜测。紧接着,德国乒乓球甲级联赛FC萨尔布吕肯俱乐部宣布,奥运冠军樊振东将加盟球队。俱乐部官方公告表示,前世界排名第一、国际乒坛巨星之一的樊振东将在新赛季代表球队出战德国乒乓球甲级联…