前端
CSS
https://www.cnblogs.com/dotnet261010/p/7198892.html
CSS的引入方式共有三种:
行内样式:使用style属性引入CSS样式
内部样式表:在style标签中书写CSS代码
外部样式表
1、样式优先级
行内样式>内部样式>外部样式(后两者是就近原则)
2、选择器优先级
优先级:ID选择器>类选择器>标签选择器
padding
设置 p 元素的 4 个内边距:
p
{
padding:2cm 4cm 3cm 4cm;
}
四个属性顺序:上 右 下 左
三个属性顺序:上 左右 下
两个属性:上下 左右
一个属性:四周
margin 四个外边距,属性顺序同 padding
透明度设置
1、css rgba()设置颜色透明度,只背景透明
rgba(R,G,B,A);
R:红色值。正整数 (0~255)
G:绿色值。正整数 (0~255)
B:蓝色值。正整数(0~255)
A:透明度。取值0~1之间
.demo2{
background:rgba(255,0,0,0.5);
}
2、css opacity属性设置背景透明度,所有元素透明
opacity: value ;
value :指定不透明度,从0.0(完全透明)到1.0(完全不透明)。
opacity 属性具有继承性,会使容器中的所有元素都具有透明度;
.demo2{
opacity:0.5;
}
标签
标签定义超链接,用于从一张页面链接到另一张页面
猜想:其余标签 href 无效
设置背景图片
<style>
body{background-image:url("{{ url_for('static',filename='/img/bj.png') }}")}
</style>
css圆角
border-radius: 5px;
label标签
label 元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性
如果您在 label 元素内点击文本,就会触发此控件,就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上
jQuery
基础内容查看jQuery笔记
- ES6新增了
let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效 - val() - 设置或返回表单字段的值
CDN 引入
<head>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</head>
例:search搜索
{% block middle %}
<p>
<input type="text" placeholder="search" name="search">
<input type="button" value="search" id="search">
</p>
<table border="1" cellspacing="0" id="table1">
<tr>
<td>index</td>
<td>username</td>
<td>phone</td>
<td>datatime</td>
<td>action</td>
</tr>
{% for user in users %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ user.username }}</td>
<td>{{ user.phone }}</td>
<td>{{ user.rdatetime }}</td>
<td>
<a href="javascript:;">update</a>
<a href="javascript:;">delete</a>
</td>
</tr>
{% endfor %}
</table>
{% block js %}
<script>
$('#search').click(
//因为#search是上边search按钮的id标签选择器名
function () {
let content = $("input[name='search']").val()
//所声明的变量,只在let命令所在的代码块内有效
//#input[name='search]使用这个进行选择的原因:找到对应元素
//<-元素选择器 标签选择器->let content = $("#input[name='search']").val()要提前指定id
// 类选择器$("#input[name='search']").val()
alert(content);
}
)
</script>
{% endblock %}
{% endblock %}
例:Tab 标签选择栏
{% block mycontent %}
<div id="middle">
<div id="left">
<div>
<button type="button" class="btn btn-default">个人中心</button>
<button type="button" class="btn btn-primary">信息修改</button>
</div>
</div>
<div id="right">
<div class="info_tab">content</div>
<div class="info_tab">content</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
$(function () {
$('.info_tab').hide();
$('.info_tab').first().show();
{#排他思想#}
$('#left button').each(function (i) {
$(this).click(function () {
$('.info_tab').hide();
$('.info_tab').eq(i).show();
})
})
})
</script>
{% endblock %}
vscode 和 pychram 等都支持快捷语句
table>tr>td{图片$}*4
按下 tab 键
<table>
<tr>
<td>图片1</td>
<td>图片2</td>
<td>图片3</td>
<td>图片4</td>
</tr>
</table>
submit和button的区别
一、submit
1、提交按钮,点击之后直接将数据提交的服务器端。
2、使用submit后,页面支持键盘enter键操作。
3、submit需要有表单时,提交时才会带数据。
4、当有表单的时候,如果提交的数据很多,那么使用submit比button要好,可以减少很多数据的获取动作,但是要记得表单提交的数据要验证。
二、button
简单的按钮,有按钮的一些事务处理,有脚本就通过脚本将参数传过去。
button默认是不提交任何数据。
如果没有表单的话,又想通过提交某些数据给后台进行回应,则需要通过button,当然使用submit也可以,但是前提要拦截onclick事件。
table标签
https://www.w3school.com.cn/tags/tag_th.asp
- table 表格
- tr 行
- th 定义表格内的表头单元格
- td 单元格
label标签
<form>
<label for="male">Male</label>
<input type="radio" name="sex" id="male" />
<br />
<label for="female">Female</label>
<input type="radio" name="sex" id="female" />
</form>
Bootstrap
Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架,Bootstrap 是基于 HTML、CSS、JAVASCRIPT 的
文档:https://v3.bootcss.com/getting-started/
与前端相关优质项目
-
React 用于构建用户界面的 JavaScript 框架
-
svelte Svelte 是构建 Web 应用程序的一种新方法
-
nodejs Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境
-
npm NPM(node package manager)是 Node.js 世界的包管理器
-
vue.js Vue.js - 是一套构建用户界面的渐进式框架
-
Markdown Markdown 是一种轻量级标记语言
Bootstrap CDN
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
Flask插件
Flask-Bootstrap 不支持 Bootstrap4
Bootstrap-Flask 轻量级代替品
这两个不能共存
使用需要安装,注意是虚拟环境,安装后就不需要下载bootstrap或者使用cdn了
pip install -i https://pypi.tuna.tsinghua.edu.cn/simpletensorflow
使用 Flask-Bootstrap
配置初始化Flask
#ext/__init__.py
from flask_bootstrap import Bootstrap
bootstrap=Bootstrap()
#apps/__init__.py
from ext import db, bootstrap
create_app()
# 初始化bootstrap
# db.init_app(app=app) 和数据库db的格式相同
bootstrap.init_app(app=app)
继承
{#base.html#}
{% extends 'bootstrap/base.html' %}
{% block title %}
index
{% endblock %}
内置 block 块
-
title
-
navbar
-
content
-
styles
-
scripts
-
head 注意:是head,不是heads
-
body
简单实例
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/user/register">register</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">login</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
{% block mycontent %}
{% endblock %}
<div class="container">
<div class="page-header">
{% block page_content %}{% endblock %}
</div>
</div>
{% endblock %}
Ajax
异步 JavaScript 和 XML
jQuery load() 方法
$(selector).load(URL,data,callback);
# 入口函数
必需的 URL 参数规定您希望加载的 URL。
可选的 data 参数规定与请求一同发送的查询字符串键/值对集合。
可选的 callback 参数是 load() 方法完成后所执行的函数名称。
实例:实现用户注册手机号信息判断
templates/register.html
......
{% block scripts %}
{{ super() }}
<script>
{#当输入域失去焦点 (blur) #}
$('#inputPhone').blur(function () {
let phone = $(this).val();
let span_ele = $(this).next('span');
if (phone.length == 11){
span_ele.css({"color":"#ff0011", "font-size": "12px"})
span_ele.text('')
$.get('{{ url_for('user.check_phone') }}',{phone:phone},function (data){
{# data为返回数据 #}
if(data.code != 200){
span_ele.text('用户已注册')
}
})
}
else{
span_ele.css({"color":"#ff0011", "font-size": "12px"})
{#注意是字典格式#}
span_ele.text('手机号长度有误')
}
})
</script>
{% endblock %}
apps/user/view.py
......
@user_bp.route('check_phone', methods=['GET', 'POST'], endpoint='check_phone')
def check_phone():
phone = request.args.get('phone')
user = User.query.filter(User.phone == phone).all()
print(user)
# code:200可用 400不可用
if len(user) > 0:
return jsonify(code=400)
else:
return jsonify(code=200)
# ajax 返回的数据必须是 json 格式 jsonify()内部封装了 json.dumps()
#或者:
if len(user) > 0:
code=400
else:
code=200
return jsonify(code=code)
只要是每一种逻辑都有对应 return 不至于找不到返回内容即可
用户操作
<!--show.html-->
<tr>
<td>{{ loop.index }}</td>
<td>{{ user.username }}</td>
<td>{{ user.phone }}</td>
<td>{{ user.rdatetime }}</td>
<td>
<a href="{{ url_for('user.update') }}?id={{ user.id }}">update</a>
<a href="{{ url_for('user.delete') }}?id={{ user.id }}">delete</a>
{#错误写法 <a href="/{{ url_for('user.delete') }}?id={{ user.id }}">delete</a> #}
{# 这样写他就找 / #}
</tr>
查询
<div>
<p>
<input type="text" placeholder="search" name="search">
<input type="button" value="search" id="search">
</p>
</div>
# view.py
@user_bp.route('/search', endpoint='search')
def user_search():
if request.args.get('search'):
keyword = request.args.get('search')
user_list = User.query.filter(or_(User.username.contains(keyword), User.username.contains(keyword))).all()
# 查询手机号或者用户名
return render_template('user/show.html', users=user_list)
else:
return redirect(url_for('user.show'))
逻辑删除
就相当于更新,在数据库里添加isdelete字段进行判断
#model.py
from datetime import datetime
from ext import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(15), nullable=False)
password = db.Column(db.String(64), nullable=False)
phone = db.Column(db.String(11), unique=True)
rdatetime = db.Column(db.DateTime, default=datetime.now)
isdelete = db.Column(db.Boolean, default=False)
# 逻辑删除字段
#view.py
@user_bp.route('/delete', endpoint='delete')
def user_delete():
id = request.args.get('id')
user = User.query.get(id)
user.isdelete = True
db.session.commit()
return redirect(url_for('user.show'))
@user_bp.route('/show', endpoint='show')
def user_show():
users = User.query.filter(User.isdelete == False).all()
return render_template('user/show.html', users=users)
删除后数据库里依然有数据
bug:注册用户时手机号因为是unqiue的,如果手机号重复会报错
解决:注册时加一层数据库查询手机号,如果手机号存在则更新用户其他数据
物理删除
@user_bp.route('/delete', endpoint='delete')
def user_delete():
id = request.args.get('id')
user = User.query.get(id)
db.session.delete(user)
db.session.commit()
return redirect(url_for('user.show'))
不需要单独设置 isdelete 列
更新用户
#view.py
@user_bp.route('/update', endpoint='update', methods=['GET', 'POST'])
def user_update():
if request.method == 'POST':
id = request.form.get('id')
new_user = User.query.get(id)
new_user.username = request.form.get('new_username')
new_user.password = request.form.get('new_password')
new_user.phone = request.form.get('new_phone')
# 只有添加数据是用 db.sesson.add()
db.session.commit()
return redirect(url_for('user.show'))
else:
id = request.args.get('id')
user = User.query.get(id)
return render_template('user/update.html', user=user)
<!--update.html-->
{% extends 'base.html' %}
{% block middle %}
<form action="{{ url_for('user.update') }}" method="post">
<p><input type="hidden" name="id" value="{{ user.id }}"></p>
{# 隐藏表单提交用户id #}
<p><input type="text" name="new_username" value="{{ user.username }}"></p>
<p><input type="text" name="new_password" placeholder="new password"></p>
<p><input type="text" name="new_phone" value="{{ user.phone }}"></p>
<p><input type="submit" value="submit"></p>
</form>
{% endblock %}
多表关系
文档:http://www.pythondoc.com/flask-sqlalchemy/models.html#one-to-many
表与表间的关系可能十分复杂
#### 外键
外键表示了两个关系之间的相关联系,以另一个关系的外键作主关键字的表被称为主表,具有此外键的表被称为主表的从表,外键又称作外关键字
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
外键是从表中的一个字段
一对多
one to many项目完成总览
user/model.py
from datetime import datetime
from ext import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(15), nullable=False)
password = db.Column(db.String(64), nullable=False)
phone = db.Column(db.String(11), unique=True)
email = db.Column(db.String(25))
icon = db.Column(db.String(40))
isdelete = db.Column(db.Boolean, default=False)
rdatetime = db.Column(db.DateTime, default=datetime.now)
articles = db.relationship('Article', backref='user')
# 用于反向查找,实际不会添加到数据库中,相当于把两张表连起来
# 和外键匹配
# Article 为模型名大写
# user小写因为表名在数据库中为小写
article/model.py
from datetime import datetime
from ext import db
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
pdatetim = db.Column(db.DateTime, default=datetime.now)
click_num = db.Column(db.Integer, default=0)
save_num = db.Column(db.Integer, default=0)
love_num = db.Column(db.Integer, default=0)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# 外键,这个字段会添加数据库中
@article_bp.route('/all')
def all_article():
articles = Article.query.all()
return render_template('article/all.html', articles=articles)
@article_bp.route('/all1')
def all_by_id():
id = request.args.get('id')
users = User.query.get(id)
return render_template('article/all1.html', users=users)
article/add_article.html
<form action="{{ url_for("article.publish") }}" method="post">
<p><input type="text" name="title" placeholder="文章标题"></p>
<p><textarea cols="50" rows="10" name="content" placeholder="输入文章内容"></textarea></p>
<p>
user:
<select name="uid">
<option value="0">please select user</option>
{% for user in users %}
<option value="{{ user.id }}">{{ user.username }}</option>
{% endfor %}
</select>
</p>
<p><input type="submit" value="add article"></p>
</form>
article/all.html
<!--文章->查user.username-->
{% for article in articles %}
<div id="article">
<p>
<h3>{{ article.title }}</h3>
<div>作者:{{ article.user.username }}</div>
{# backref 反向引用 #}
<p>
{{ article.content }}
</p>
<div>{{ article.pdatetime }}</div>
</p>
</div>
{% endfor %}
article/all1.html
{#根据用户id找文章#}
{% for article in users.articles %}
<div id="article">
<h1>
{{ article.title }}
</h1>
<div>
{{ users.username }}
</div>
<p>
{{ article.content }}
</p>
<p>
{{ article.pdatetim }}
</p>
</div>
{% endfor %}
多对多
many to many
可以通过中间表建立关系
pycharm查看表关系
首先选择多个表->diagrams->show
多对多实例:用户与商品
功能:用户和商品展示,用户购卖商品,根据商品找用户,根据用户找商品
上图为实际表关系
apps/goods/model.py
from apps.user.model import User
from ext import db
class Goods(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
gname = db.Column(db.String(100), nullable=False)
price = db.Column(db.Float, nullable=False)
# back reference
users = db.relationship('User', backref='goodslist', secondary='user_goods')
# relationship 定义的字段不会填入数据库,是给view和template定义的
# users和User表建立联系但是没有外键,所以需要 secondary适用于多对多情况
# Goods可以通过 Goods.users.xxx 查users表 反向:users.googlist.xxx查Goods表
# relationship 定义在Goods表或User表都可以,只是需要改值
# 中间表 添加表不需要执行 python app.py db init 此命令尽在没有migrations文件夹时需执行,添加字段也是migrate即可
class User_goods(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
goods_id = db.Column(db.Integer, db.ForeignKey(Goods.id), nullable=False)
num = db.Column(db.Integer, default=0)
apps/user/model.py
from datetime import datetime
from ext import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(15), nullable=False)
password = db.Column(db.String(64), nullable=False)
phone = db.Column(db.String(11), unique=True)
email = db.Column(db.String(25))
icon = db.Column(db.String(40))
isdelete = db.Column(db.Boolean, default=False)
rdatetime = db.Column(db.DateTime, default=datetime.now)
#或者在此添加 #goods=db.relationship('Goods',backref='userlist',secondary='user_goods')
apps/goods/view.py
from flask import Blueprint, render_template, request
from apps.goods.model import Goods, User_goods
from apps.user.model import User
from ext import db
goods_bp = Blueprint('goods', __name__)
@goods_bp.route('/')
def goods_index():
users_list = User.query.all()
goods_list = Goods.query.all()
return render_template('goods/show.html', users_list=users_list, goods_list=goods_list)
@goods_bp.route('/buy')
def goods_buy():
ug = User_goods()
# 先创建 User_goods 的实例化对象
ug.user_id = request.args.get('uid')
ug.goods_id = request.args.get('gid')
db.session.add(ug)
# 添加 ug 对象到缓存中
db.session.commit()
return 'add ok'
# 根据商品找用户
@goods_bp.route('/finduser', endpoint='finduser')
def find_user():
goods_id = request.args.get('gid')
goods = Goods.query.get(goods_id)
return render_template('goods/finduser.html', goods=goods)
# 根据用户找商品
@goods_bp.route('/findgoods', endpoint='findgoods')
def find_goods():
user_id = request.args.get('uid')
users = User.query.get(user_id)
return render_template('goods/findgoods.html', users=users)
templates/goods/show.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>show</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<style>
#goods_table tr td{
width: 100px;
background-color: cornflowerblue;
}
</style>
</head>
<body>
<div>
<form>
<p>
{#展示用户#}
<select name="uid" >
<option value="0">please select user</option>
{% for users in users_list %}
<option value="{{ users.id }}">{{ users.username }}</option>
{% endfor %}
</select>
</p>
{#展示商品#}
<table border="1px solid" cellspacing="0" id="goods_table" style="text-align: center">
<tr>
<th>index</th>
<th>goods</th>
<th>price</th>
<th>action</th>
</tr>
{% for goods in goods_list %}
<tr>
<td>{{ loop.index }}</td>
<td><a href="{{ url_for('goods.finduser') }}?gid={{ goods.id }}">{{ goods.gname }}</a></td>
{# 建立超链接 #}
<td>{{ goods.price }}</td>
<td><input type="button" class="btngoods" value="buy" tag="{{ goods.id }}"></td>
{# 通过tag标签把goods.id值传给view #}
</tr>
{% endfor %}
</table>
</form>
</div>
<script>
$('.btngoods').click(function () {
goods_id = $(this).attr('tag');
user_id = $("select[name='uid']").val();
{# css选择器 选择具有值为uid的name属性的select标签 注意引号 #}
location.href='/buy?uid='+user_id+'&'+'gid='+goods_id;
})
</script>
</body>
</html>
templates/goods/finduser.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>finduser</title>
</head>
<body>
<div>
<h3>{{ goods.gname }}</h3>
<br>
{% for user in goods.users %}
<p>
<a href="{{ url_for('goods.findgoods') }}?uid={{ user.id }}">{{ user.username }}</a>
</p>
{% endfor %}
</div>
</body>
</html>
templates/goods/findgoods.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>findgoods</title>
</head>
<body>
<div>
<h3>{{ users.username }}</h3>
</div>
{% for goods in users.goodslist %}
<p>{{ goods.gname }}------{{ goods.price }}</p>
{% endfor %}
</body>
</html>
app.py
from apps.user.model import User
from apps.article.model import Article
from apps.goods.model import *
# 一定要将表对象在此引用否则无法在数据库生成表
多对多实例2:用户,文章,评论
from datetime import datetime
from apps.user.model import User
from ext import db
class Atype(db.Model):
__tablename__ = 'A_type'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
type_name = db.Column(db.String(25), nullable=False)
articles = db.relationship('Article', backref='typename')
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
pdatetime = db.Column(db.DateTime, default=datetime.now)
click_num = db.Column(db.Integer, default=0)
save_num = db.Column(db.Integer, default=0)
love_num = db.Column(db.Integer, default=0)
type_id = db.Column(db.Integer, db.ForeignKey('A_type.id'))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
comments = db.relationship('Comment', backref='articles')
class Comment(db.Model):
# 自定义表名
__tablename__ = 'comment'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
comment = db.Column(db.String(255), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
article_id = db.Column(db.Integer, db.ForeignKey('article.id'))
# 注意 db.ForeignKey() 格式
cdatetime = db.Column(db.DateTime, default=datetime.now)
def __str__(self):
return self.comment
注意:用pycharm查看时 atype 没有连起来(可能没成功建立起外键关系),试了很多次都是这样
ELSE
补充蓝图
#apps/user/__init__.py
user_bp = Blueprint('user', __name__, url_prefix='/user')
@user_bp.route('/', endpoint='index')
def index():
return 'index'
#添加url_prefix后
Map([<Rule '/user/' (HEAD, OPTIONS, GET) -> user.index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
# url_prefix='/user' 必须是加 /
ValueError: urls must start with a leading slash
数据加密
除了 hashlib 库,Flask 自带了hash加盐加密方法
加密:generate_password_hash
from werkzeug.security import generate_password_hash
#generate_password_hash(password,method='pbkdf2:sha256',salt_length=*)
user.password = generate_password_hash(password)
# 数据格式:method$salt$hash
注意:加密后长度是变化的,所以数据库 password 字段要注意长度
Flask 由于封装问题修改数据字段长度在进行 migrate 会显示 not update,Flask 只能是添加删除数据库字段可会显示有更新,可以通过 pycharm 更改
Django 都可以
密码匹配:check_password_hash
@user_bp.route('/login', methods=['GET', 'POST'], endpoint='login')
def user_login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
users = User.query.filter(User.username == username).all()
for user in users:
flag = check_password_hash(user.password, password)
# flask 封装检查password函数,返回值bool类型,匹配为Ture,否则为False
#user.password 为加密过的密码,password 为未加密密码
if flag:
return jsonify(msg='登录成功', code=200)
else:
return jsonify(msg='登录失败', code=400)
else:
return render_template('user/login.html')
会话机制
记录用户登陆状态
HTTP是无状态协议
Cookie,Session
Cookie
保存
# 通过 response 对象保存
response = redirect(xxx)
response = render_template(xxx)
response = Response()
response = make_response(xxx)
response = jsonify(xxx)
# 通过对象调用方法
response.set_cookie(key,value,max_age)
set_cookie(属性)
#属性
name cookie的名称
value cookie的值
expire 过期时间
path 有效路径
domain 域名
secure https专用 true为安全传输
httponly 仅通过http协议访问,不能通过js
#例:
@user_bp.route('/login', methods=['GET', 'POST'], endpoint='login')
def user_login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
users = User.query.filter(User.username == username).all()
for user in users:
flag = check_password_hash(user.password, password)
# flask 封装检查password函数,匹配为Ture,否则为False
if flag:
response = redirect(url_for('user.index'))
# 需要先创建一个 response 对象
response.set_cookie(key='username', value=username, max_age=1800)
return response
else:
return render_template('user/login.html', msg='登陆失败')
else:
return render_template('user/login.html')
获取
# 通过 request 对象获取
request.form.get()
request.args.get
cookie也在 request 对象中
request.cookies.get(key) ---> value
#例:
@user_bp.route('/', endpoint='index')
def index():
username = request.cookies.get('username')
return render_template('user/index.html', username=username)
删除
# 通过 response 对象删除
response = redirect(xxx)
response = render_template(xxx)
response = Response()
response = make_response(xxx)
response = jsonify(xxx)
# 通过对象调用方法
response.delete_cookie(key)
#例:
@user_bp.route('/logout',endpoint='logout')
def user_logout():
response=redirect(url_for('user.login'))
response.delete_cookie('username')
return response
Session
在服务器保存,字典类型
cookie,session结合使用,cookie存储session_id,具体数据存储在session
需要设置 setting.py
SECRET_KEY = ‘xxxxx’
xx这个值自己定义即可,目的是用于 sessionid 的加密
#例:settings.py
class Config:
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@127.0.0.1:3306/flask'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
SECRET_KEY='fdsajfidposajgiopds'
设置:
若果要使用session,需要直接导入:
from flask import session
session[key]=value
会将key=value保存到session内存空间中
#例
@user_bp.route('/login', methods=['GET', 'POST'], endpoint='login')
def user_login():
......
session['username'] = username
return redirect(url_for('user.index'))
......
获取
value = session[key] 或 value = session.get(key)
#例:
@user_bp.route('/', endpoint='index')
def index():
username = session['username']
return render_template('user/index.html', username=username)
清除
session.clear() 删除session内存空间和删除cookie,(常用)
del session[key] 只会删除session中的这个键值对,不会删除session空间和cookie
# 例
@user_bp.route('/logout', endpoint='logout')
def user_logout():
session.clear()
return redirect(url_for('user.index'))
手机验证码
云短信服务 SMS(Short Message Service,SMS)
可以找各家提供的免费赠送的部分,有些是产生验证码返回给后台,有的是后台产生验证码发送给 SMS,再发送给前端
网易开发文档:https://support.dun.163.com/documents/2018101001?docId=210168759616458752
控制台:https://dun.163.com/dashboard#/m/sms/index/?pid=YD00030772740753
查看凭证
签名需要实名认证审核
例:手机验证码登录
具体页面效果
taskId的调用-使用session实现
apps/user/smssend.py —sms开发文档中有
from hashlib import md5
import json
import random
import time
import urllib
import urllib.request
class SmsSendAPIDemo(object):
"""易盾短信发送接口示例代码"""
API_URL = "https://sms.dun.163.com/v2/sendsms"
VERSION = "v2"
def __init__(self, secret_id, secret_key, business_id):
"""
Args:
secret_id (str) 产品密钥ID,产品标识
secret_key (str) 产品私有密钥,服务端生成签名信息使用
business_id (str) 业务ID,易盾根据产品业务特点分配
"""
self.secret_id = secret_id
self.secret_key = secret_key
self.business_id = business_id
def gen_signature(self, params=None):
"""生成签名信息
Args:
params (object) 请求参数
Returns:
参数签名md5值
"""
buff = ""
for k in sorted(params.keys()):
buff += str(k) + str(params[k])
buff += self.secret_key
return md5(buff.encode("utf-8")).hexdigest()
def send(self, params):
"""请求易盾接口
Args:
params (object) 请求参数
Returns:
请求结果,json格式
"""
params["secretId"] = self.secret_id
params["businessId"] = self.business_id
params["version"] = self.VERSION
params["timestamp"] = int(time.time() * 1000)
params["nonce"] = int(random.random() * 100000000)
params["signature"] = self.gen_signature(params)
try:
params = urllib.parse.urlencode(params)
params = params.encode('utf-8')
request = urllib.request.Request(self.API_URL, params)
content = urllib.request.urlopen(request, timeout=5).read()
return json.loads(content)
except Exception as ex:
print("调用API接口失败:", str(ex))
apps/user/view.py 主要逻辑功能实现,可以把发送验证码功能单独封装
import hashlib
from flask import Blueprint, render_template, request, url_for, redirect, jsonify, session
from werkzeug.security import generate_password_hash, check_password_hash
from apps import db
from apps.user.model import User
from apps.user.smssend import SmsSendAPIDemo
user_bp = Blueprint('user', __name__, url_prefix='/user')
@user_bp.route('/', endpoint='index')
def index():
uid = session.get('uid')
# 如果没有 uid 对应的值,也就是取不到的话值为 None
if uid:
user = User.query.get(uid)
return render_template('user/index.html', user=user)
return render_template('user/index.html')
@user_bp.route('/center', endpoint='center')
def center():
uid = session.get('uid')
if uid:
user = User.query.get(uid)
return render_template('user/center.html', user=user)
return render_template('user/center.html')
@user_bp.route('/register', methods=['GET', 'POST'], endpoint='register')
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
repassword = request.form.get('repassword')
phone = request.form.get('phone')
email = request.form.get('email')
print(email)
if password == repassword:
user = User()
user.username = username
user.password = generate_password_hash(password)
user.phone = phone
user.email = email
db.session.add(user)
db.session.commit()
return 'ok'
else:
return 'password is diff'
else:
return render_template('user/register.html')
@user_bp.route('/check_phone', methods=['GET', 'POST'], endpoint='check_phone')
def check_phone():
phone = request.args.get('phone')
user = User.query.filter(User.phone == phone).all()
print(user)
# code:200可用 400不可用
if len(user) > 0:
code = 400
else:
code = 200
return jsonify(code=code)
# ajax 返回的数据必须是 json 格式 jsonify()内部封装了 json.dumps()
@user_bp.route('/login_check_phone', methods=['GET', 'POST'], endpoint='login_check_phone')
def login_check_phone():
phone = request.args.get('phone')
user = User.query.filter(User.phone == phone).all()
print(user)
# code:200已注册 400未注册
if len(user) > 0:
code = 200
else:
code = 400
return jsonify(code=code)
# ajax 返回的数据必须是 json 格式 jsonify()内部封装了 json.dumps()
@user_bp.route('/login', methods=['GET', 'POST'], endpoint='login')
def user_login():
if request.method == 'POST':
# f=1 账户密码登录 f=2 手机号验证码登录
if request.args.get('f') == '1':
username = request.form.get('username')
password = request.form.get('password')
users = User.query.filter(User.username == username).all()
for user in users:
flag = check_password_hash(user.password, password)
# flask 封装检查password函数,匹配为Ture,否则为False
if flag:
session['uid'] = user.id
return redirect(url_for('user.center'))
else:
return render_template('user/login.html', msg='登陆失败')
elif request.args.get('f') == '2':
# '2' 一定要加引号,因为request.args.get('f')接受的是字符串类型
phone = request.form.get('phone')
code = request.form.get('code')
valid_code = session.get('code')
if code == valid_code:
# 先验证 验证码是否正确
user = User.query.filter(User.phone == phone).first()
if user:
session['uid'] = user.id
return redirect(url_for('user.center'))
else:
return render_template('user/login.html', msg='验证码有误')
else:
return 'request params is worry'
else:
return render_template('user/login.html')
@user_bp.route('/logout', endpoint='logout')
def user_logout():
session.clear()
return redirect(url_for('user.login'))
@user_bp.route('/send_code', endpoint='send_code')
def send_code():
# phone = request.args.get('phone')
# SECRET_ID = "05c6ee1b7319dfd6aad7e2c51b00cc73" # 产品密钥ID,产品标识
# SECRET_KEY = "7042637886876a15101994b371f169d3" # 产品私有密钥,服务端生成签名信息使用,请严格保管,避免泄露
# BUSINESS_ID = "225e296526b24078887d47a06dd234b7" # 业务ID,易盾根据产品业务特点分配
# api = SmsSendAPIDemo(SECRET_ID, SECRET_KEY, BUSINESS_ID)
# params = {
# "mobile": "your mobile",
# "templateId": "10084",
# "paramType": "json",
# "params": "json格式字符串"
# # 国际短信对应的国际编码(非国际短信接入请注释掉该行代码)
# # "internationalCode": "对应的国家编码"
# }
# ret = api.send(params)
session['code'] = '12345'
return jsonify(code=200, msg='短信发送成功')
# if ret is not None:
# if ret["code"] == 200:
# taskId = ret["data"]["taskId"]
# # 收到验证码 taskId 是局部变量 login() 没法调用
# # 如果将 taskId 设为全局变量多用户登录时将存在逻辑问题
# session[phone] = taskId
# return jsonify(code=200, msg='短信发送成功')
# else:
# return jsonify(code=400, msg='短信发送失败')
templates/user/login.html
{% extends 'base.html' %}
{% block styles %}
{{ super() }}
{# 加了super就是继承原有的样式 #}
<style>
#container {
width: 400px;
margin: 20px auto;
}
</style>
{% endblock %}
{% block mycontent %}
<form class="form-horizontal" id="container" method="post" action="{{ url_for('user.login') }}?f=1">
<div>
<ul class="nav nav-tabs" id="loginselect">
<li role="presentation" id="passwordlogin"><a>账号密码登录</a></li>
<li role="presentation" id="codelogin"><a>手机验证码登录</a></li>
</ul>
</div>
<br>
<div id="div1">
<div class="form-group">
<label for="inputUsername" class="col-sm-2 control-label">username</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputUsername" placeholder="Username" name="username">
</div>
</div>
<div class="form-group">
<label for="inputPassword" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword" placeholder="Password"
name="password">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-md-10">
<button type="submit" class="btn btn-default" id="sign">Sign in</button>
</div>
<p>{{ msg }}</p>
</div>
</div>
</form>
<form class="form-horizontal" id="container" method="post" action="{{ url_for('user.login') }}?f=2">
<div id="div2">
<div class="form-group">
<label for="inputphone" class="col-sm-0 control-label"></label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputphone" placeholder="phone" name="phone">
<span></span>
</div>
</div>
<div class="form-group" style=" float: left ">
<label for="inputcode" class="col-sm-0 control-label"></label>
<div class="col-sm-10">
<input type="text" class="form-control" col-sm-offset-10 id="inputcode" placeholder="code"
name="code">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-0 col-md-2" style="float: left">
<button type="button" class="btn btn-default" id="sendcode" name="code">send code</button>
{# 此处type类型未button,点击不提交整个表单,只是触发jquery #}
</div>
</div>
<div class="form-group" style="float: left">
<div class="col-sm-offset-10 col-md-2">
<button type="submit" class="btn btn-default" id="codesign">Sign in</button>
</div>
<p>{{ msg }}</p>
</div>
</div>
</form>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
$(document).ready(function () {
$('#div2').hide()
$('#div1').show()
$('#passwordlogin').addClass('active')
})
$('#passwordlogin').click(function () {
$('#div2').hide()
$('#div1').show()
$('#passwordlogin').addClass('active')
$('#codelogin').removeClass('active')
})
$('#codelogin').click(function () {
$('#div1').hide()
$('#div2').show()
$('#codelogin').addClass('active')
$('#passwordlogin').removeClass('active')
})
$('#inputphone').blur(function () {
let phone = $(this).val();
let span_ele = $(this).next('span');
if (phone.length == 11) {
span_ele.css({"color": "#ff0011", "font-size": "12px"})
span_ele.text('')
$.get('{{ url_for('user.login_check_phone') }}', {phone: phone}, function (data) {
{# data为返回数据 #}
if (data.code != 200) {
span_ele.text('用户未注册')
}
})
} else {
span_ele.css({"color": "#ff0011", "font-size": "12px"})
{#注意是字典格式#}
span_ele.text('手机号长度有误')
}
})
$('#sendcode').click(function () {
let span_ele2 = $(this).next('span');
let phone = $(this).val();
$.get('{{ url_for('user.send_code') }}', {phone: phone}, function (data) {
if (data.code == 200) {
alert(data.msg);
}
else{
alert(data.msg);
}
})
})
</script>
{% endblock %}
templates/user/register.html
{% extends 'base.html' %}
{% block styles %}
{{ super() }}
{# 加了super就是继承原有的样式 #}
<style>
#container {
width: 400px;
margin: 20px auto;
}
</style>
{% endblock %}
{% block mycontent %}
<form class="form-horizontal" id="container" method="post" action="{{ url_for('user.register') }}">
<div class="form-group">
<label for="inputUsername" class="col-sm-2 control-label">username</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputUsername" placeholder="Username" name="username">
</div>
</div>
<div class="form-group">
<label for="inputPassword" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword" placeholder="Password" name="password">
</div>
</div>
<div class="form-group">
<label for="inputRePassword" class="col-sm-2 control-label">Confirm Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputRePassword" placeholder="Confirm Password"
name="repassword">
</div>
</div>
<div class="form-group">
<label for="inputPhone" class="col-sm-2 control-label">Phone</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPhone" placeholder="Phone" name="phone">
<span></span>
</div>
</div>
<div class="form-group">
<label for="inputEmail" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail" placeholder="email" name="email">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-md-10">
<button type="submit" class="btn btn-default">Sign in</button>
</div>
</div>
</form>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
{#当输入域失去焦点 (blur) #}
$('#inputPhone').blur(function () {
let phone = $(this).val();
let span_ele = $(this).next('span');
if (phone.length == 11){
span_ele.css({"color":"#ff0011", "font-size": "12px"})
span_ele.text('')
$.get('{{ url_for('user.check_phone') }}',{phone:phone},function (data){
{# data为返回数据 #}
if(data.code != 200){
span_ele.text('用户已注册')
}
})
}
else{
span_ele.css({"color":"#ff0011", "font-size": "12px"})
{#注意是字典格式#}
span_ele.text('手机号长度有误')
}
})
</script>
{% endblock %}
/center
权限验证
目的:只要走center路由,就判断用户是否登录,若登录则正常显示,没有登陆的话跳转到登录页面
Flask 钩子函数![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/3fdd6bc54c1349fc91dcbb8d65ad3b21.png)
-
直接应用在 app 上:
before_first_request #在处理第一个请求前运行装饰的要运行的视图函数, 只会执行一次 *before_request #在每次请求前运行 after_request #如果没有未处理的异常抛出,在每次请求后运行,需要接收一个 Response 类的对象作为参数 并返回一个新的Response 对象 或者 直接返回接受到的Response 对象 teardown_request #在每次请求后运行,即使有未处理的异常
-
应用到蓝图:
before_app_first_request *before_app_request after_app_request teardown_app_request
以装饰器的形式存在
钩子函数全局有效,设置一次,其他路由直接添加到对应列表即可
flask.g
g 对象,本次请求的对象
尽在当次请求中有效
实例
require_login_list = ['/user/center']
#@user_bp.before_app_request
#def checkveir():
# if request.path in require_login_list:
# uid = session['uid']
# if not uid:
# return redirect(url_for('user.index'))
# else:
# user = User.query.get(uid)
# g.user = user
# 这样写如果没有登陆,就取不到uid,无法继续执行
def checkveir():
if request.path in require_login_list:
try:
id = session['uid']
user = User.query.get(id)
g.user = user
except:
return redirect(url_for('user.login'))
......
@user_bp.route('/center', endpoint='center')
def center():
uid = session.get('uid')
if uid:
user = User.query.get(uid)
return render_template('user/center.html', user=g.user)
return render_template('user/center.html')
其他功能
文件上传![1](https://i-blog.csdnimg.cn/blog_migrate/9dce39825751a6ce85cf959103aa3541.gif)
上传界面html
<form action="{{ url_for('user.change') }}" method="post" enctype="multipart/form-data">
{# 图片上传必须设置enctype #}
<input type="file" id="exampleInputFile" name="icon">
<img src="{% if user.icon %} {{ url_for('static',filename=user.icon ) }}{% else %}{{ url_for('static',filename='img/1.jpg') }}{% endif %}" alt="" width="90" height="92">
{# 图片使用固定的引入方式,也最好是加一层判断 #}
</form>
print( icon ) icon是一个文件对象,使用 icon.filename 获取文件名
view.py change
@user_bp.route('/change', methods=['GET', 'POST'], endpoint='change')
def change():
if request.method == 'POST':
# id = request.form.get('id') 无需这样写,用不着传id,因为session里边就有uid
username = request.form.get('username')
email = request.form.get('email')
phone = request.form.get('phone')
icon = request.files.get('icon')
# 有文件就要使用 request.files.get()方式
icon_filename = icon.filename
# icon 是一个对象,icon.filename 取出他的名字
suffix = icon_filename.rsplit('.')[-1]
# 取出文件后缀名
if suffix in ALLOWED_EXTENSIONS:
icon_filename = secure_filename(icon_filename)
# 封装的文件名转换函数,下划线替换空格等,使文件名符合 python 文件名规则
file_path = os.path.join(Config.UPLOAD_ICON_DIR, icon_filename)
# 文件路径拼接上文件名称
icon.save(file_path)
# 保存文件
users = User.query.all()
for user in users:
print(user.phone)
if phone == g.user.phone or phone != user.phone:
user = g.user
user.username = username
user.phone = phone
user.email = email
#icon_path_and_name = '/upload/icon/'+icon_filename
# 此处写相对路径即可,因为{{ url_for('static',filename=user.icon ) }}前段时static目录下找
#user.icon = icon_path_and_name 可以加一个判断如果文件名没传过来就让path为空,或者是数据库只保存文件名前端这样去找 {{ url_for('static',filename='upload/icon/'+user.icon ) }}
db.session.commit()
return render_template('user/center.html', user=g.user)
else:
return render_template('user/center.html', msg='手机号重复', user=g.user)
# 遍历判断手机号是否重复
return render_template('user/center.html', user=g.user)
return render_template('user/center.html', user=g.user)
settings.py
class Config:
......
BASE_DIR = os.path.dirname(os.path.abspath(__name__))
# 项目路径
STATIC_DIR = os.path.join(BASE_DIR, 'static')
# 静态文件的路径
TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')
UPLOAD_ICON_DIR = os.path.join(STATIC_DIR, 'upload/icon')
# 头像上传目录
UPLOAD_PHOTO_DIR = os.path.join(STATIC_DIR, 'upload/photo')
# 相册的上传目录
富文本编辑器
TingMCE web 富文本编辑器,可以嵌入到任意Web应用中使用
同类程序有:UEditor、Kindeditor、Simditor、CKEditor、wangEditor、Suneditor、froala等等
开发文档:http://tinymce.ax-z.cn/quick-start.php
- 经典模式
- 内联模式(inline) 页面的编辑视图与阅读视图合二为一
- 沉浸无干扰模式(distraction-free)
使用步骤
第1步:引入 TingMCE 脚本
在中插入如下这行代码
<script src="{{ url_for('static', filename='tinymce/tinymce.min.js') }}"></script>
第2步:将TinyMCE初始化为页面的一部分
需要使用**tinymce.init()**来进行初始化。
tinymce.init()内的初始化对象包含众多参数,但都是可省略的,唯一必须的参数就是selector(允许通过css选择器指定TinyMCE要绑定的内容容器,传统模式需指定textarea,内联或沉浸模式可指定div或其它块元素,推荐div)
$(function () {
tinymce.init({
selector: '.mytextarea',
// 使用CSS选择器语法来确定页面上哪个元素被TinyMCE替换成编辑器
// 与页面关联,可以用类选择器,也可以用标签选择器,前后对应即可
height:400,
inline:false,
// selector的目标设为textarea是无法启用内联模式的,必须是普通的块元素,div最佳
plugins:'advlist autolink link image lists preview', //字符串方式
// plugins : ['advlist','autolink','link'], //数组方式
// 启用插件非常简单,只需将插件名作为参数,多个插件用空格分隔的字符串,也支持使用数组的方式
toolbar:true,
// 工具栏
menubar:true,
// 菜单栏
// ........
});
})
base.html 引入tingmce
{% block head %}
{# 注意是head,不是heads #}
<head>
{{ super() }}
<title>ocean</title>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
{# 注意先导入jqeury,在引入tingmce #}
<script type="text/javascript" src="{{ url_for('static', filename= 'js/center.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename= 'tinymce/tinymce.min.js') }}"></script>
</head>
{% endblock %}
center.html
<textarea class="mytextarea" cols="30" rows="10">hello</textarea>
tingmce 文章内容格式会转换成 html 标签格式存储到数据库,因为富文本有很多符号,表情不能以字符串方式存储,将content行改为二进制存储格式
取出的过程也要转换,下图箭头表示为转换
需要自定义一个过滤器
#user/view.py
@user_bp.app_template_filter('cdecode')
def content_decode(content):
content = content.decode('utf-8')
return content[:200]
# 只取前200个字,没有效果
#index.html
{% block mycontent %}
{% for article in articles %}
<div id="art_div">
<h3>{{ article.title }}</h3>
<P>{{ article.content | cdecode | safe | truncate(200) }}</P>
</div>
<br>
{% endfor %}
{% endblock %}
Tingmce 会自动对内容里出现的 html 标签进行转义
出现 样式 标签可能会有冲突
文章分页
数据分页查询
# 4 3 2 | 1 因为只有四篇
pagination = Article.query.order_by(-Article.pdatetime).paginate(page=2, per_page=3)
# 返回一个 pagination 对象,page是查询第几页,per_page每页几条
pagination.items
# 返回对象列表 [<Article 4>, <Article 3>, <Article 2>]
pagination.page
# 当前的页码数 1
pagination.prev_num
# 当前的前一个页码数 none
pagination.next_num
# 当前的后一个页码数 2
pagination.has_next
# 有没有下一页 True
pagination.has_prev
# 有没有上一页 False
pagination.pages
# 总页数 2
实例:
apps/user/view.py
@user_bp.route('/', endpoint='index')
def index():
uid = session.get('uid',None)
# None 为默认值
# uid = session[uid]这样写如果uid取不到会报错Key error
page = request.args.get('page', 1)
# 默认值为 1
pagination = Article.query.order_by(-Article.pdatetime).paginate(page=page, per_page=3)
art_type = Art_type.query.all()
# 按照文章发表时间倒序排序
if uid:
user = User.query.get(uid)
return render_template('user/index.html', user=user, art_type=art_type, pagination=pagination)
return render_template('user/index.html', pagination=pagination)
templates/user/index.html
{% block mycontent %}
{# 文章部分 #}
{% for article in pagination.items %}
<div id="art_div">
<h4>{{ article.title }}</h4>
<p class="anchor_text">{{ article.users.username }}</p>
{# 通过User relationship backref逆向查找 #}
<P>{{ article.content | cdecode | safe }}</P>
<div class="art_div_foot">
<p style="width: 200px">发布时间:{{ article.pdatetime }}</p>
<p>点击数:{{ article.click_num }}</p>
<p>收藏数:{{ article.save_num }}</p>
<p>点赞数:{{ article.lovw_num }}</p>
</div>
</div>
<br>
{% endfor %}
{# 分页栏 #}
<nav aria-label="Page navigation" class="col-md-4 col-md-offset-4">
<ul class="pagination">
{# 判断是否存在上一页,决定是否可以点击,设置disabled属性 #}
<li {% if not pagination.has_prev %}
class="disabled"
{% endif %} >
<a href="{{ url_for('user.index') }}?page={{ pagination.prev_num }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{# 判断页码于查询页码是否一致,一致重定向 #}
{% for page_num in range(1,pagination.pages + 1) %}
<li {% if pagination.page == page_num %} class="active" {% endif %}><a
href="{{ url_for('user.index') }}?page={{ page_num }}">{{ page_num }}</a></li>
{% endfor %}
<li {% if not pagination.has_next %}
class="disabled"
{% endif %} >
<a href="{{ url_for('user.index') }}?page={{ pagination.next_num }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
pycharm 折叠代码快捷键
- 所有代码折叠:ctrl+alt±
- 所有代码展开:ctrl+alt++
- 折叠某一点:ctrl±
- 展开某一层:ctrl++
点赞收藏
以点赞为例
article.py
@article_bp1.route('/love', endpoint='article_love')
def art_love():
art_id = request.args.get('art_id')
tag = request.args.get('tag')
article = Article.query.get(art_id)
if tag == '1':
article.love_num -= 1
else:
article.love_num += 1
db.session.commit()
return jsonify(num=article.love_num)
{% block newcontent %}
<div class="container">
<h2>{{ article.title }}</h2>
<div class="art_div_foot">
<span id="right_part">发布时间:{{ article.pdatetime }}</span>
<span id="left_part">
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span>
<span tag="0">{{ article.click_num }}</span>
<span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span>
<span tag="0">{{ article.love_num }}</span>
<span class="glyphicon glyphicon-star-empty" aria-hidden="true"></span>
<span tag="0">{{ article.save_num }}</span>
</span>
</div>
<br>
<p>{{ article.content | cdecode| safe }}</p>
</div>
{% endblock %}
# ajax
{% block scripts %}
{{ super() }}
<script>
$(function () {
$('.glyphicon-thumbs-up').click(function () {
let $this = $(this);
let tag = $this.next('span').attr('tag');
{#tag=0 表示为点赞,tag=1表示已点赞#}
$.get("{{ url_for('article.article_love') }}", {art_id:{{ article.id }}, tag: tag}, function (data) {
$this.next('span').text(data.num);
})
{#点赞#}
if (tag == 1) {
$this.css({'color':'black'});
$this.next('span').attr('tag', "0");
} else if (tag == 0) {
$this.css({"color":"red"});
$this.next('span').attr('tag', "1");
}
})
})
</script>
{% endblock %}
有一个漏洞就是没有用户记录状态,刷新后就可以再次点赞
应该再建一张表格保存,用户点赞过的文章id
收藏原理相同
云存储
七牛云控制台:https://portal.qiniu.com/kodo/overview
开发文档:https://developer.qiniu.com/kodo
py软件开发工具包:https://developer.qiniu.com/kodo/1242/python
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple qiniu
图片上传
utils/utils.py
# -*- coding: utf-8 -*-
# flake8: noqa
import random
from qiniu import Auth, put_file, etag, put_data
import qiniu.config
def upload_qiniu(filestorage):
# 需要填写你的 Access Key 和 Secret Key
access_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
secret_key = 'xxxxxxxxxxxxxxxxxxx'
# 构建鉴权对象
q = Auth(access_key, secret_key)
# 要上传的空间
bucket_name = 'oceanpic'
# 上传后保存的文件名
filename = filestorage.filename
ran = random.randint(1, 1000)
suffix = filename.rsplit('.')[-1]
key = filename.rsplit('.')[0] + '_' + str(ran) + '.' + suffix
# 生成上传 Token,可以指定过期时间等
token = q.upload_token(bucket_name, key, 3600)
# 要上传文件的本地路径,put_file指定路径上传
# localfile = './sync/bbb.jpg'
# ret, info = put_file(token, key, localfile)
# 上传二进制流 put_data 是七牛库里封装的函数
ret, info = put_data(token, key, filestorage.read())
# ret 和 info 是对象,属性是 json 字符串类型数据
# filestorge.read() 二进制方式读取
return ret, info
ret,info
ret
{'hash': 'Fq5QJYBt6mGL54sEJxHa26j5BqeQ', 'key': 'u=433095846,3800291930&fm=111&gp=0_521.jpg'}
info
_ResponseInfo__response:<Response [200]>, exception:None, status_code:200, text_body:{"hash":"Fq5QJYBt6mGL54sEJxHa26j5BqeQ","key":"u=433095846,3800291930\u0026fm=111\u0026gp=0_521.jpg"
}, req_id:wYkAAACFs_aNpl0W, x_log:X-Log
user/view.py
@user_bp.route('/upload_photo', methods=['GET', 'POST'])
def upload_photo():
photo = request.files.get('photo')
# FileStorage 对象,photo.filename获取文件名,photo.save(path)可以保存文件
if photo:
ret, info = upload_qiniu(photo)
if info.status_code == 200:
# 将文件名保存到数据库
photo = Photo()
photo.photo_name = ret['key']
# 取出文件名
photo.user_id = g.user.id
db.session.add(photo)
db.session.commit()
# 添加数据
return redirect(url_for('user.center'))
return render_template('user/center.html', user=g.user, msg='文件上传失败')
return redirect(url_for('user.center'))
别忘了把 /user/upload_photo 加到钩子函数列表中
图片显示
七牛私有空间没法分享外链
@user_bp.route('/photograph', endpoint='photograph')
def show_photo():
page = int(request.args.get('page', 1))
photos = Photo.query.paginate(page=page, per_page=8)
return render_template('user/photo.html', photos=photos, user=g.user)
html
{% for photo in photos.items %}
<div id="show_photo_large_container">
<img class="show_photo_large" src="http://qn3yof5yl.hb-bkt.clouddn.com/{{ photo.photo_name }}"
alt="img is lost">
</div>
{% endfor %}
图片删除
utils/utils.py
def del_qiniu(photo_name):
access_key = 'l3JQ8x0z4Ly5L_65QEL2VKQCz2gH0bi0GD-A6Qoa'
secret_key = 'RB1Wouc_TO6ais15icDd7YHs-PXnhtEQWovMJBGB'
# 初始化Auth状态
q = Auth(access_key, secret_key)
# 初始化BucketManager
bucket = BucketManager(q)
# 你要测试的空间, 并且这个key在你空间中存在
bucket_name = 'oceanpic'
key = photo_name
# 删除bucket_name 中的文件 key
ret, info = bucket.delete(bucket_name, key)
return ret, info
view.py
@user_bp.route('/del_photo', endpoint='del_photo')
def del_photo():
photo_id = request.args.get('pid')
photo = Photo.query.get(photo_id)
photo_name = photo.photo_name
user_id = g.user.id
del_user_id = photo.user_id
if user_id == del_user_id:
ret, info = del_qiniu(photo_name)
if info.status_code == 200:
db.session.delete(photo)
db.session.commit()
return redirect(url_for('user.center'))
else:
return render_template('404.html', msg='危险越权操作!已记录您的IP')
return render_template('404.html')
防止越权
@article_bp1.route('/delete', endpoint='delete')
def del_article():
aid = request.args.get('aid')
user_id = g.user.id
# 判断删除文章是不是当前用户的,防止越权操作,这种只能删除当前图片
referer = request.headers['referer']
article_user = Article.query.get(aid)
del_user_id = article_user.user_id
if user_id == del_user_id:
db.session.delete(article_user)
db.session.commit()
return redirect(url_for('user.center'))
else:
return render_template('404.html', msg='危险越权操作!已记录您的IP', referer=referer)
# aid = request.args.get('aid')
# user_id = g.user.id
# # 判断删除文章是不是当前用户的,防止越权操作,能删除当前用户的图片
# user_art_list = Article.query.filter(Article.user_id == user_id).all()
# referer = request.headers['referer']
# for article in user_art_list:
# if int(aid) == int(article.id):
# flag = True
# break
# else:
# flag = False
# if flag:
# article = Article.query.get(aid)
# db.session.delete(article)
# db.session.commit()
# return redirect(url_for('user.center'))
# else:
# return render_template('404.html', msg='危险越权操作!已记录您的IP', referer=referer)
一对一关系
Flask 没有预设一对一关系,一对多默认返回列表
只能是从表的外键使用 unique 进行约束
主表查询时使用 [0] 进行截取第一个对象
但是这样操作可能会报错,需要在查询时加异常处理
try:
except:
et_key)
# 初始化BucketManager
bucket = BucketManager(q)
# 你要测试的空间, 并且这个key在你空间中存在
bucket_name = ‘oceanpic’
key = photo_name
# 删除bucket_name 中的文件 key
ret, info = bucket.delete(bucket_name, key)
return ret, info
view.py
```python
@user_bp.route('/del_photo', endpoint='del_photo')
def del_photo():
photo_id = request.args.get('pid')
photo = Photo.query.get(photo_id)
photo_name = photo.photo_name
user_id = g.user.id
del_user_id = photo.user_id
if user_id == del_user_id:
ret, info = del_qiniu(photo_name)
if info.status_code == 200:
db.session.delete(photo)
db.session.commit()
return redirect(url_for('user.center'))
else:
return render_template('404.html', msg='危险越权操作!已记录您的IP')
return render_template('404.html')
防止越权
@article_bp1.route('/delete', endpoint='delete')
def del_article():
aid = request.args.get('aid')
user_id = g.user.id
# 判断删除文章是不是当前用户的,防止越权操作,这种只能删除当前图片
referer = request.headers['referer']
article_user = Article.query.get(aid)
del_user_id = article_user.user_id
if user_id == del_user_id:
db.session.delete(article_user)
db.session.commit()
return redirect(url_for('user.center'))
else:
return render_template('404.html', msg='危险越权操作!已记录您的IP', referer=referer)
# aid = request.args.get('aid')
# user_id = g.user.id
# # 判断删除文章是不是当前用户的,防止越权操作,能删除当前用户的图片
# user_art_list = Article.query.filter(Article.user_id == user_id).all()
# referer = request.headers['referer']
# for article in user_art_list:
# if int(aid) == int(article.id):
# flag = True
# break
# else:
# flag = False
# if flag:
# article = Article.query.get(aid)
# db.session.delete(article)
# db.session.commit()
# return redirect(url_for('user.center'))
# else:
# return render_template('404.html', msg='危险越权操作!已记录您的IP', referer=referer)