引言
1.工程运行的依赖见requirements.txt,data.db是数据库文件。
2.工程运行方法,打开pycharm,选择该工程,选择pycharm底部的终端。
3.cd进入到工程文件夹MyTest,输入flask run,即可生成一个本地网址,进入该网址即可。
4.其中Full_version.py是所有后端flask代码集合的一个文件。实际运行中,我们把该文件内的代码分类保存在了watchlist文件夹中的五个.py文件中。
5.最原始的工程结构如下,把Full_version.py改名为app.py,将工程结构恢复如下,终端输入python app.py即可运行。
6.管理员登录账户:
账号名:cz
密码:123
7.新增管理员账户方法如下:
打开工程后,终端输入flask admin,依次输入新增用户名,密码。
功能简要说明
- 登入登出管理员
- 添加、更新、删除电影名
- 登入后,修改管理员用户名
- 未登录前,自动跳到登录界面
后端完整代码
Full_version.py
# 该文件夹是包含了本工程所有的后端flask函数代码
from flask import Flask
from werkzeug.security import generate_password_hash, check_password_hash
from flask import request, url_for, redirect, flash
import os
import click
import sys
from flask_login import login_required, logout_user
from flask_login import login_user,current_user,UserMixin
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, render_template
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.root_path, 'data.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 关闭对模型修改的监控
db = SQLAlchemy(app) # 初始化扩展,传入程序实例 app
# flash() 函数在内部会把消息存储到 Flask 提供的 session 对象里。
# session 用来在请求间存储数据,它会把数据签名后存储到浏览器的 Cookie 中,所以我们需要设置签名所需的密钥:
app.config['SECRET_KEY'] = 'dev' # 等同于 app.secret_key = 'dev'
# 在扩展类实例化前加载配置
login_manager = LoginManager(app) # 实例化扩展类
# 未登录的用户访问对应的 URL,Flask-Login 会把用户重定向到登录页面,并显示一个错误提示。
# 为了让这个重定向操作正确执行,我们还需要把 login_manager.login_view 的值设为我们程序的登录视图端点(函数名)
login_manager.login_view = 'login'
# 存储用户信息的类
# 添加 username 字段和 password_hash 字段,分别用来存储登录所需的用户名和密码散列值
# 继承 Flask-Login 提供的 UserMixin 类,拥有几个用于判断认证状态的属性和方法,
# 其中最常用的是 is_authenticated 属性:如果当前用户已经登录,那么 current_user.is_authenticated 会返回 True
class User(db.Model,UserMixin): # 表名将会是 user(自动生成,小写处理)
id = db.Column(db.Integer, primary_key=True) # 主键
name = db.Column(db.String(20)) # 名字
# 同时添加两个方法来实现设置密码和验证密码的功能
username = db.Column(db.String(20)) # 用户名
password_hash = db.Column(db.String(128)) # 密码散列值
def set_password(self, password): # 用来设置密码的方法,接受密码作为参数
self.password_hash = generate_password_hash(password) # 将生成的密码保持到对应字段
def validate_password(self, password): # 用于验证密码的方法,接受密码作为参数
return check_password_hash(self.password_hash, password) # 返回布尔值
class Movie(db.Model): # 表名将会是 movie
id = db.Column(db.Integer, primary_key=True) # 主键
title = db.Column(db.String(60)) # 电影标题
year = db.Column(db.String(4)) # 电影年份
# 自定义命令初始化数据库
@app.cli.command() # 注册为命令
@click.option('--drop', is_flag=True, help='Create after drop.') # 设置选项
def initdb(drop):
"""Initialize the database."""
if drop: # 判断是否输入了选项
db.drop_all()
db.create_all()
click.echo('Initialized database.') # 输出提示信息
# 初始化 Flask-Login,其包含实现用户认证需要的各类功能函数
@login_manager.user_loader
def load_user(user_id): # 创建用户加载回调函数,接受用户 ID 作为参数
user = User.query.get(int(user_id)) # 用 ID 作为 User 模型的主键查询对应的用户
return user # 返回用户对象
# 创建自定义命令forge
@app.cli.command()
def forge():
"""产生虚拟数据.执行flask forge就会把虚拟数据添加进数据库"""
db.create_all()
# 全局的两个变量移动到这个函数内
name = 'Grey Li'
movies = [
{'title': 'My Neighbor Totoro', 'year': '1988'},
{'title': 'Dead Poets Society', 'year': '1989'},
{'title': 'A Perfect World', 'year': '1993'},
{'title': 'Leon', 'year': '1994'},
{'title': 'Mahjong', 'year': '1996'},
{'title': 'Swallowtail Butterfly', 'year': '1996'},
{'title': 'King of Comedy', 'year': '1999'},
{'title': 'Devils on the Doorstep', 'year': '1999'},
{'title': 'WALL-E', 'year': '2008'},
{'title': 'The Pork of Music', 'year': '2012'},
]
user = User(name=name)
db.session.add(user)
for m in movies:
movie = Movie(title=m['title'], year=m['year'])
db.session.add(movie)
db.session.commit()
click.echo('Done.')
# 生成管理员账户
@app.cli.command()
@click.option('--username', prompt=True, help='The username used to login.')
@click.option('--password', prompt=True, hide_input=False, confirmation_prompt=True, help='The password used to login.')
def admin(username, password):
"""Create user."""
db.create_all()
user = User.query.first()
if user is not None:
click.echo('Updating user...')
user.username = username
user.set_password(password) # 设置密码
else:
click.echo('Creating user...')
user = User(username=username, name='Admin')
user.set_password(password) # 设置密码
db.session.add(user)
db.session.commit() # 提交数据库会话
click.echo('Done.')
# 模板上下文处理函数
@app.context_processor
def inject_user(): # 函数名可以随意修改
user = User.query.first()
# 函数返回的变量(以字典键值对的形式)将会统一注入到每一个模板(首页模板,404错误页面模板)的上下文环境
# 中,因此可以直接在模板中使用
return dict(user=user) # 需要返回字典,等同于 return {'user': user}
@app.route('/',methods=['GET', 'POST'])
# 其中 GET 请求用来获取资源,而 POST 则用来创建 / 更新资源。
def index():
if request.method == 'POST': # 判断是否是 POST 请求
# 获取表单数据
if not current_user.is_authenticated: # 如果当前用户未认证
return redirect(url_for('index')) # 重定向到主页
title = request.form.get('title') # 传入表单对应输入字段的 name 值
year = request.form.get('year')
# 验证数据
if not title or not year or len(year) > 4 or len(title) > 60:
# flash页面上显示一个提示消息
flash('Invalid input.') # 显示错误提示
return redirect(url_for('index')) # 重定向回主页
# 保存表单数据到数据库
movie = Movie(title=title, year=year) # 创建记录
db.session.add(movie) # 添加到数据库会话
db.session.commit() # 提交数据库会话
flash('Item created.') # 显示成功创建的提示
return redirect(url_for('index')) # 重定向回主页
# user = User.query.first() # 读取用户记录
movies = Movie.query.all() # 读取所有电影记录
return render_template('index.html', movies=movies)
# 404 错误处理函数
@app.errorhandler(404) # 传入要处理的错误代码
def page_not_found(e): # 接受异常对象作为参数
return render_template('404.html'), 404 # 返回模板和状态码
# 更新电影条目
@app.route('/movie/edit/<int:movie_id>', methods=['GET', 'POST'])
@login_required
def edit(movie_id):
# movie_id 变量是电影条目记录在数据库中的主键值
# get_or_404() 方法,它会返回对应主键的记录,如果没有找到,则返回 404 错误响应
movie = Movie.query.get_or_404(movie_id)
if request.method == 'POST': # 处理编辑表单的提交请求
title = request.form['title']
year = request.form['year']
if not title or not year or len(year) != 4 or len(title) > 60:
flash('Invalid input.')
return redirect(url_for('edit', movie_id=movie_id)) # 重定向回对应的编辑页面
movie.title = title # 更新标题
movie.year = year # 更新年份
db.session.commit() # 提交数据库会话
flash('Item updated.')
return redirect(url_for('index')) # 重定向回主页
return render_template('edit.html', movie=movie) # 传入被编辑的电影记录
# 删除电影条目
@app.route('/movie/delete/<int:movie_id>', methods=['POST']) # 限定只接受 POST 请求
# 对于不允许未登录用户访问的视图,只需要为视图函数附加一个 login_required 装饰器就可以将未登录用户拒之门外
@login_required # 登录保护
def delete(movie_id):
movie = Movie.query.get_or_404(movie_id) # 获取电影记录
db.session.delete(movie) # 删除对应的记录
db.session.commit() # 提交数据库会话
flash('Item deleted.')
return redirect(url_for('index')) # 重定向回主页
# 用户登录页面
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if not username or not password:
flash('Invalid input.')
return redirect(url_for('login'))
user = User.query.first()
# 验证用户名和密码是否一致
if username == user.username and user.validate_password(password):
login_user(user) # 登入用户
flash('Login success.')
return redirect(url_for('index')) # 重定向到主页
flash('Invalid username or password.') # 如果验证失败,显示错误消息
return redirect(url_for('login')) # 重定向回登录页面
return render_template('login.html')
# 用户登出
@app.route('/logout')
@login_required # 用于视图保护,后面会详细介绍
def logout():
logout_user() # 登出用户
flash('Goodbye.')
return redirect(url_for('index')) # 重定向回首页
# 支持用户设计名字
@app.route('/settings', methods=['GET', 'POST'])
@login_required
def settings():
if request.method == 'POST':
name = request.form['name']
if not name or len(name) > 20:
flash('Invalid input.')
return redirect(url_for('settings'))
current_user.name = name
# current_user 会返回当前登录用户的数据库记录对象
# 等同于下面的用法
# user = User.query.first()
# user.name = name
db.session.commit()
flash('Settings updated.')
return redirect(url_for('index'))
return render_template('settings.html')
if __name__ == '__main__':
app.run(debug=True)