Flask框架---模板,数据库,蓝图

Jinjia2模板

模板其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体的值需要从使用的数据中获取
使用真实值替换变量,再返回最终得到的字符串
使用模板的好处
视图函数只负责业务逻辑和数据处理(业务逻辑方面)
而模板则取到视图函数的数据结果进行展示(视图展示方面)
代码结构清晰,耦合度低

模板的使用
{{}} 来表示变量名,这种 {{}} 语法叫做变量代码块
用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句,以及后面会介绍的继承
使用 {# #} 进行注释,注释的内容不会在html中被渲染出来

具体代码:
1.定义相关视图函数,使用render_template(.html,相关参数)渲染模板

@app.route('/')
def index():
    # 往模板中传入的数据
    my_str = 'Hello'
    my_int = 10
    my_array = [3, 4, 2, 1, 7, 9]
    my_dict = {
        'name': 'xiaoming',
        'age': 18
    }
    return render_template('temp_demo1.html',
                           my_str=my_str,
                           my_int=my_int,
                           my_array=my_array,
                           my_dict=my_dict
                           )

2.定义模板文件(前端显示的内容)
注意前后端的变量名必须相同
同时还可以对后端的传参在模板文件中进行简单的过滤筛选以及计算
当后端传递的是列表或者字典的时候,前端可以直接按照python的操作习惯来获取特定的值,如list[0],dic[“key”]

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
我的模板html内容
<br/>{{ my_str }}
<br/>{{ my_int }}
<br/>{{ my_array }}
<br/>{{ my_dict }}
计算参数
<br/> my_int + 10 的和为:{{ my_int + 10 }}
<br/> my_int + my_array第0个值的和为:{{ my_int + my_array[0] }}
<br/> my_array 第0个值为:{{ my_array[0] }}
<br/> my_array 第1个值为:{{ my_array.1 }}
<br/> my_dict 中 name 的值为:{{ my_dict['name'] }}
<br/> my_dict 中 age 的值为:{{ my_dict.age }}
</body>
</html>

过滤器的相关使用方法
过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。
过滤器的使用方法:

{{variable | filter_name(*args)}} 如果没有参数传递
{{variable | filter_name}}
{{ "hello world" | reverse | upper }} #过滤器的链式调用 先翻转再变大写

字符串的相关过滤器:
safe:禁用转义

<p>{{ '<em>hello</em>' | safe }}</p>

capitalize:把变量值的首字母转成大写,其余字母转小写

<p>{{ 'hello' | capitalize }}</p>

lower:把值转成小写,upper:把值转大写

<p>{{ 'HELLO' | lower }}</p>

title:把值中的每个单词的首字母都转成大写

<p>{{ 'hello world' | title }}</p>

reverse:字符串反转

<p>{{ 'olleh' | reverse }}</p>

format:格式化输出,注意格式化输出的时候占位符和print参数是一样

<p>{{ '%s is %d' | format('name',17) }}</p>

striptags:渲染之前把值中所有的HTML标签都删掉,算是一个高级的用法吧

<p>{{ '<em>hello</em>' | striptags }}</p>

列表相关过滤器:

first:取第一个元素;last:取最后一个元素

<p>{{ [1,2,3,4,5,6] | first }}</p>

length:获取列表长度

<p>{{ [1,2,3,4,5,6] | length }}</p>

sum:列表求和

<p>{{ [1,2,3,4,5,6] | sum }}</p>

sort:列表排序

<p>{{ [6,2,3,1,5,4] | sort }}</p>

自定义过滤器:
当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:
一种是通过Flask应用对象的 add_template_filter 方法
另一种是通过装饰器来实现自定义过滤器

例如自定义列表的反转:
1.定义列表反转的实现函数
2.add_template_filter(函数名,过滤器名)

def do_listreverse(li):
    temp_li = list(li)
    temp_li.reverse()
    return temp_li

app.add_template_filter(do_listreverse,'lireverse')

利用装饰器实现自定义过滤器
@app.template_filter(‘自定义过滤器名字’)
个人习惯用方法一

@app.template_filter('lireverse')
def do_listreverse(li):
    temp_li = list(li)
    temp_li.reverse()
    return temp_li

控制代码快:

if控制语句:

{% if comments | length > 0 %}
    There are {{ comments | length }} comments
{% else %}
    There are no comments
{% endif %}

for循环控制语句:

{% for post in posts if post.text %}
    <div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text | safe }}</p>
    </div>
{% endfor %}

for循环中的一些常用属性:
在这里插入图片描述
cycle函数会在每次循环的时候,返回其参数中的下一个元素,可以拿上面的例子来说明:

{% for post in posts%}
    {{loop.cycle('odd','even')}} {{post.title}}
{% endfor %}

模板的复用:
模板的复用有三种方法,宏,继承,包含
宏就相当于一个函数,需要先定义:{% macro 函数名(参数1,参数2…)%},再调用:和调用函数是一样的,函数名+参数
代码示例:

#定义宏
{% macro input(name,value='',type='text') %}
    <input type="{{type}}" name="{{name}}"
        value="{{value}}" class="form-control">
{% endmacro %}
#调用宏
{{ input('name' value='zs')}}

继承:个人比较推荐这种写法,简称挖坑哈哈。
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
定义父模板:

{% block top %}
  顶部菜单
{% endblock top %}

{% block content %}
{% endblock content %}

{% block bottom %}
  底部
{% endblock bottom %}

子模板

{% extends 'base.html' %}
{% block content %}
 需要填充的内容
{% endblock content %}

这时候子模板就继承了base的top和bottom而覆盖了content内容了
注意:不支持多继承,即一个字模板只能继承一个父模板,而且每个block的内容不能相同。

WTF表单

表单常用字段
在这里插入图片描述
表单常用的验证函数:
在这里插入图片描述

#原始的HTML表单:
<form method="post">
    <label>用户名:</label><input type="text" name="username" placeholder="请输入用户名"><br/>
    <label>密码:</label><input type="password" name="password" placeholder="请输入密码"><br/>
    <label>确认密码:</label><input type="password" name="password2" placeholder="请输入确认密码"><br/>
    <input type="submit" value="注册">
</form>

{% for message in get_flashed_messages() %}
    {{ message }}
{% endfor %}

#使用WTFORM:
<form method="post">
    {{ form.username.label }} {{ form.username }}<br/>
    {{ form.password.label }} {{ form.password }}<br/>
    {{ form.password2.label }} {{ form.password2 }}<br/>
    {{ form.submit }}
</form>

#后端验证函数:
from flask import Flask,render_template, flash
from flask_wtf import FlaskForm
from wtforms import SubmitField,StringField,PasswordField
from wtforms.validators import DataRequired,EqualTo
app = Flask(__name__)
app.config['SECRET_KEY']='SECRET_KEY'

class RegisterForms(FlaskfForms):
    username = StringField("用户名:", validators=[DataRequired("请输入用户名")], render_kw={"placeholder": "请输入用户名"})
    password = PasswordField("密码:", validators=[DataRequired("请输入密码")])
    password2 = PasswordField("确认密码:", validators=[DataRequired("请输入确认密码"), EqualTo("password", "两次密码不一致")])
    submit = SubmitField("注册")

	@app.route('/demo2', methods=["get", "post"])
	def demo2():
		register_form=RegisterForm()
		if rgieter_form.validate_on_submit:#验证
			username = request.form.get("username")
	        password = request.form.get("password")
	        password2 = request.form.get("password2")
	   else:
	   	           flash("参数有误或者不完整")
		return render_template('temp_register.html', form=register_form)

if __name__ == '__main__':
    app.run(debug=True)
        

上述视图函数做了如下工作:
1.当点击提交按钮的时候,确保username ,password ,password 2都有值
2.确保password 和password 2的值相当,一旦上述两个条件有一个不满足会提示参数错误

CSRF

什么叫csrf呢?简单讲就是:用户a正常访问网站b在没有退出网站b的情况下访问了钓鱼网站c,网站c获取用户的cookie信息后对用户发出非法请求
在这里插入图片描述
防止CSRF原理:

在客户端向后端请求界面数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值
在 Form 表单中添加一个隐藏的的字段,值也是 csrf_token
在用户点击提交的时候,会带上这两个值向后台发起请求
后端接受到请求,以会以下几件事件:
从 cookie中取出 csrf_token
从 表单数据中取出来隐藏的 csrf_token 的值
进行对比
如果比较之后两值一样,那么代表是正常的请求,如果没取到或者比较不一样,代表不是正常的请求,不执行下一步操作

在flaskWTFORM中实现防攻击:
1.设置应用程序的 secret_key,用于加密生成 csrf_token 的值

app.secret_key = "#此处可以写随机字符串#"

2.在模板的表单中添加csrf_token

<form method="post">
    {{ form.csrf_token() }}
    {{ form.username.label }} {{ form.username }}<br/>
    {{ form.password.label }} {{ form.password }}<br/>
    {{ form.password2.label }} {{ form.password2 }}<br/>
    {{ form.submit }}
</form>

#ORM
ORM:即采用面向对象的方法来操作数据库
优点
只需要面向对象编程, 不需要面向数据库编写代码., 对数据库的操作都转化成对类属性和方法的操作,不用编写各种数据库的sql语句,实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异,不在关注用的是mysql、oracle…等通过简单的配置就可以轻松更换数据库, 而不需要修改代码.
缺点

相比较直接使用SQL语句操作数据库,有性能损失.
根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.
原理:
在这里插入图片描述

Flask-SQLAlchemy

在flask中Flask-SQLAlchemy直接封装了ORM用来操作数据库
1.安装:pip install flask-sqlalchemy
2.连接MySQL需要安装mysqldb:pip install flask-mysqldb
sqlalchemy常用字段:
在这里插入图片描述
常用的sqlalchemy约束:
在这里插入图片描述
常用的sqlalchemy查询过滤器:
在这里插入图片描述
常见的查询执行器:
在这里插入图片描述
用一个实例演示上面的知识点:

#############建表#######################
class Role(db.Model):
    # 定义表名
    __tablename__ = 'roles'
    # 定义列对象
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    us = db.relationship('User', backref='role') """一对多关系,将relationship定义在一
    的一方,第一个参数接关联的类名,第二个参数接表名,意思
    :roles.us可直接返回role底下关联的user,由于有backref属性,
    users.role可直接返回user对应的角色"""

    #repr()方法显示一个可读字符串
    def __repr__(self):
        return 'Role:%s'% self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64),unique=True)
    password = db.Column(db.String(64))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

参数lazy决定了什么时候SQLALchemy从数据库中加载数据
如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
设置为 subquery 的话,role.users 返回所有数据列表
另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式
设置为 dynamic 的话,role.users 返回查询对象,并没有做真正的查询,可以利用查询对象做其他逻辑,比如:先排序再返回结果
**多对多关系:**需要定义一张中间表来存放映射关系

tb_student_course = db.Table('tb_student_course',
                             db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
                             db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
                             )


class Student(db.Model):
    __tablename__ = "students"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)

    courses = db.relationship('Course', secondary=tb_student_course,
                              backref='student',
                              lazy='dynamic')


class Course(db.Model):
    __tablename__ = "courses"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)

在多对多关系中,关系字段随便指定在哪张表都可以。secondary指定多对多关系中关系表的名字
创建表:

db.create_all()

删除表

db.drop_all()

插入一条数据,就相当于对类进行初始化

ro1 = Role(name='admin')
db.session.add(ro1)
db.session.commit()
#再次插入一条数据
ro2 = Role(name='user')
db.session.add(ro2)
db.session.commit()

使用add_all一次性添加多条数据

us1 = User(name='wang',email='wang@163.com',password='123456',role_id=ro1.id)
us2 = User(name='zhang',email='zhang@189.com',password='201512',role_id=ro2.id)
us3 = User(name='chen',email='chen@126.com',password='987654',role_id=ro2.id)
us4 = User(name='zhou',email='zhou@163.com',password='456789',role_id=ro1.id)
us5 = User(name='tang',email='tang@itheima.com',password='158104',role_id=ro2.id)
us6 = User(name='wu',email='wu@gmail.com',password='5623514',role_id=ro2.id)
us7 = User(name='qian',email='qian@gmail.com',password='1543567',role_id=ro1.id)
us8 = User(name='liu',email='liu@itheima.com',password='867322',role_id=ro1.id)
us9 = User(name='li',email='li@163.com',password='4526342',role_id=ro2.id)
us10 = User(name='sun',email='sun@163.com',password='235523',role_id=ro2.id)
db.session.add_all([us1,us2,us3,us4,us5,us6,us7,us8,us9,us10])
db.session.commit()

filter_by精确查询

User.query.filter_by(name='wang').all()
User.query.all()
User.query.first()

filter模糊查询

User.query.filter(User.name.endswith('g')).all()

get():参数为主键,如果主键不存在没有返回内容

User.query.get()

逻辑与或非

from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()
User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()

delete删除

user = User.query.first()
db.session.delete(user)
db.session.commit()

更新,没有update而是先查找再赋值覆盖
user = User.query.first()
user.name = ‘dong’
db.session.commit()

数据库迁移

什么情况下需要进行数据库迁移呢?在建立好数据库相关数据表之后如果对数据表的结构进行了更改,最直接的方式就是删除旧表,但这样会丢失数据,那么可以采用数据库的迁移,将最新的数据表同步到数据库当中
1.安装flask-migrate

pip install flask-migrate

2.执行init创建迁移仓库

python database.py db init

3.执行migrate创建迁移脚本

python database.py db migrate -m 'initial migration'

4.upgrade更新数据库

python database.py db upgrade

蓝图

当我们的业务逻辑负责度增强的时候,就需要模块化代码,比如一个py负责登录逻辑,一个py负责详情逻辑等,这就需要模块之间的独立和相互调用,此时就引进了蓝图,
Blueprint 是一个存储操作方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。一个应用可以具有多个Blueprint
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。

1,创建一个蓝图对象

admin=Blueprint('admin',__name__)

2,在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器

@admin.route('/')
def admin_home():
    return 'admin_home'

3,在应用对象上注册这个蓝图对象

app.register_blueprint(admin,url\_prefix='/admin')

当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数

运行机制

蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作
当在应用对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表
然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项
当执行应用对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的路由表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值