Flask 基础(三)

113 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

用户操作

<!--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>

image-20210119103919569

查询

image-20210119142433502

<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 %}

多表关系

文档:www.pythondoc.com/flask-sqlal…

表与表间的关系可能十分复杂

#### 外键

外键表示了两个关系之间的相关联系,以另一个关系的外键作主关键字的表被称为主表,具有此外键的表被称为主表的从表,外键又称作外关键字image-20210119145824052

user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

image-20210119184316860

外键是从表中的一个字段

一对多

one to manyimage-20210119210440785项目完成总览image-20210119201054575

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

image-20210119210342498

image-20210120090256933

可以通过中间表建立关系

pycharm查看表关系

首先选择多个表->diagrams->show

多对多实例:用户与商品

功能:用户和商品展示,用户购卖商品,根据商品找用户,根据用户找商品1

image-20210120144631854

上图为实际表关系

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)

image-20210120105426370

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:用户,文章,评论

image-20210119140831577

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 没有连起来(可能没成功建立起外键关系),试了很多次都是这样

image-20210120181746326

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

image-20210120193027022

数据加密

除了 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,再发送给前端

image-20210122102133931

网易开发文档:support.dun.163.com/documents/2…

控制台:dun.163.com/dashboard#/…

查看凭证

image-20210122091759603

签名需要实名认证审核

image-20210122091602427

image-20210122091449899