Flask学习笔记(二)

flask=werkzeug+sqlalchemy_jinja

Flask学习笔记(二)

  • 1.知识点
    • 1.1虚拟环境
      • 1.1.1virtualenv
      • 1.1.2virtualenvwrapper
    • 1.2web与视图
    • 1.3jinja2
      • 1.3.1template知识点
      • 1.3.2豆瓣列表页
      • 1.3.3视图知识点
    • 1.4数据库
      • 1.4.1SQLAlchemy
        • 1.4.1.1使用SQLAlchemy去连接数据库
        • 1.4.1.2将ORM模型映射到数据库中
        • 1.4.1.3用session做数据的增删改查操作
        • 1.4.1.4SQLAlchemy常用数据类型
        • 1.4.1.5Column常用参数
        • 1.4.1.6query可用参数:
        • 1.4.1.7filter过滤条件
        • 1.4.1.8外键
        • 1.4.1.9ORM关系以及一对多
        • 1.4.1.10ORM关系以及一对一
        • 1.4.1.11ORM关系以及多对多
        • 1.4.1.12ORM层面的删除数据:
        • 1.4.1.13排序:
        • 1.4.1.14limit、offset和切片操作:
        • 1.4.1.15懒加载
        • 1.4.1.16having:
        • 1.4.1.17 join:
        • 1.4.1.18subquery
      • 1.4.2Flask_SQLAlchemy
        • 1.4.2.1flask-sqlalchemy
        • 1.4.2.2alembic
        • 1.4.2.3flask-script
        • 1.4.2.3flask-migrate
    • 1.5知识点补充
      • 1.5.1Flask-WTF
        • 1.5.1.1表单验证
        • 1.5.1.2模版渲染
        • 1.5.1.3文件上传
      • 1.5.2cookie和session
        • 1.5.2.1cookie
        • 1.5.2.2session
      • 1.5.3CSRF攻击与防御
      • 1.5.4Flask上下文
      • 1.5.5钩子函数
      • 1.5.6flask信号
      • 1.5.7Flask-Restful插件
      • 1.5.8memcached
      • 1.5.9redis
  • 3.错误

1.知识点

1.1虚拟环境

1.1.1virtualenv

在系统级python环境下,安装virtualenv虚拟环境。我电脑安装了anconda,所以这一块我就不用操作了。-p参数指定使用系统级的哪一个python.exe 来执行。
win

pip install virtualenv
virtualenv abc#就会在当前地址下新建了一个环境文件夹abc
cd abc/Scripts#进入环境的scripts目录
activate#激活环境

linux

source abc/bin activate#linux是放在bin目录下的

1.1.2virtualenvwrapper

win

pip install virtualenvwrapper-win
mkvirtualenv abc#放在当前win用户的文件夹里面envs的文件夹下
workon abc#激活此环境
deactivate#推出此环境
rmvirtualenv abc#删除此环境
lsvirtualenv abc#列出环境
cdvirtualenv de#立即进入de环境

linux

pip install virtualenvwrapper

1.2web与视图

1.url:scheme://host:port/path/?query-string=xxx#anchor

2.web服务器:负责处理http请求,响应静态文件,常见的有Apache,Nginx以及微软的IIS.

3.应用服务器:负责处理逻辑的服务器。比如php、python的代码,是不能直接通过nginx这种web服务器来处理的,只能通过应用服务器来处理,常见的应用服务器有uwsgi、tomcat等。

4.web应用框架:一般使用某种语言,封装了常用的web功能的框架就是web应用框架,flask、Django以及Java中的SSH(Structs2+Spring3+Hibernate3)框架都是web应用框架。

5.config设置配置文件的四种方式:

app.config['DEBUG'] = True#1.硬编码
app.config.update(DEBUG=True)#2.因为app.config是flask.config.Config的实例,而Config类是继承自dict,因此可以通过update方法
import config
app.config.from_object(config)#3.通过模块对象,将config.py文件的数据导入
app.config.from_pyfile('settings.py',silent=True)#4.也可使用.txt后缀的文件;默认是为False,找不到配置文件就会抛出异常

6.永久性重定向:http的状态码是301,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,最经典的就是京东网站,你输入www.jingdong.com的时候,会被重定向到www.jd.com,因为jingdong.com这个网址已经被废弃了,被改成jd.com,所以这种情况下应该用永久重定向。

7.暂时性重定向:http的状态码是302,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。
8.redirect(url,code=301)#默认是302

1.3jinja2

1.3.1template知识点

1.在渲染模版的时候,默认会从项目根目录下的templates目录下查找模版。也可以在Flask初始化的时候指定template_folder来指定模版的路径。

app=Flask(__name__,template_folder="xxx")

2.当需要渲染的参数比较多时,通过关键字传参方式,给html渲染传参。

@app.route("/key")
def key():
    context={"a":1,"b":2,"c":3}
    return render_template("key.html",**context)#==render_template("key.html","a"=1,"b"=2,"c"=3)
<html>
    <head>key</head>
    <title>key</title>
    <body>
       <h1>a={{a}}</h1> 
       <h1>b={{b}}</h1> 
       <h1>c={{c}}</h1> 
    </body>
</html>

3.html中,{{ … }}:用来装载变量,或者字典的key。
{% … %}:用来装载控制语句。
{# … #}:用来装载注释
4.过滤器:通过管道符号(|)进行使用,过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。过滤器官网。

abs(value):返回一个数值的绝对值。 例如:-1|abs。
default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。name|default(‘xiaotuo’)——如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。name
or ‘xiaotuo’
escape(value)或e:转义字符,会将<、>等符号转义成HTML中的符号。例如:content|escape或content|e。({%autoescape off/on%} XXXXX{%autoescape%}关掉或开启局部转义)
first(value):返回一个序列的第一个元素。names|first。
format(value,*arags,**kwargs):格式化字符串。例如以下代码:{{ “%s” -> “%s”|format(‘Hello?’,“Foo!”) }}将输出:Helloo? - Foo!
last(value):返回一个序列的最后一个元素。示例:names|last。
length(value):返回一个序列或者字典的长度。示例:names|length。
join(value,d=u’'):将一个序列用d这个参数的值拼接成字符串。
safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:content_html|safe。
int(value):将值转换为int类型。 float(value):将值转换为float类型。
lower(value):将字符串转换为小写。 upper(value):将字符串转换为小写。
replace(value,old,new): 替换将old替换为new的字符串。
truncate(value,length=255,killwords=False):截取length长度的字符串。
striptags(value):删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。
trim:截取字符串前面和后面的空白字符。 string(value):将变量转换成字符串。
wordcount(s):计算一个长字符串中单词的个数。

5.自定义过滤器(两种方式)

app.config['TEMPLATES_AUTO_RELOAD']=True
@app.template_filter('cut')
def cut(value):
    value=value.replace("hello",'')
    return value
def datetime_format(value,format="%Y年%m月%d日 %H:%M"):
    return value.strftime(format)
app.add_template_filter(datetime_format,"dformat")#将自定义的datetime_format,以dformat为名添加到app的过滤器中

自定义时间过滤器

@app.route("/time_filter")
def time_filter():
    now=datetime(2023,6,25,10,53,20)
    return render_template("key.html",now=now)
@app.template_filter("handle_time")
def handle_time(time):
    if isinstance(time,datetime):#首先判断是否时时间类型
        now=datetime.now()
        timestamp=(now-time).total_seconds()#时间间距
        if timestamp<60:
            return "刚刚"
        elif timestamp>=60 and timestamp<60*60:
            minutes=timestamp/60
            return "%s分钟前"%int(minutes)
        elif timestamp>=60*60 and timestamp<60*60*24:
            hours=timestamp/(60*60)
            return "%s小时前"%int(hours)
        elif timestamp>=60*60*24 and timestamp<60*60*24*30:
            days=timestamp/(60*60*24)
            return "%s天前"%int(days)
        else:
            return time.strftime('%Y/%m/%d %H:%M')

    else:
        return time
<html>
    <body>
       <h1>now is :{{now|handle_time}}</h1> 
    </body>
</html>

6.for循环(for ,else ,endfor,if),不可以使用continue和break表达式来控制循环的执行

   {% for user in users %}
   <li>{{ user.username}}</li>
   {% else %}
   <li>no users found</li>
   {% endfor %}
   {% for y in range(1,10) if y<x%}{#y从1开始循环,当不满足y
参数 说明
loop.index 当前迭代的索引(从1开始)
loop.index0 当前迭代的索引(从0开始)
loop.first 是否是第一次迭代,返回True或False
loop.last 是否是最后一次迭代,返回True或False
loop.length 序列的长度

用for循环写一个99乘法表。

@app.route("/mul")
def mul():
    return render_template('/mul.html')
<table border="1">
    <tbody>
        {% for x in range(1,10) %}
        <tr>
            {% for y in range(1,x+1) %}
            <td>{{y}}*{{x}}={{x*y}}</td>
            {%endfor%}
        </tr>
        {%endfor%}
    </tbody>
</table>

7.宏:模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量。

{% macro INPUT(name="",value="",type="text") %}{# 定义一个INPUT函数 #}
    <input name="{{name}}" value="{{value}}" type="{{type}}">
{% endmacro %}

<table>
    <tr>
        <td>用户名</td>
        <td>{{INPUT("username")}}</td>
    </tr>
    <tr>
        <td>密码</td>
        <td>{{INPUT("password",type="password")}}</td>
    </tr>
    <tr>
        <td></td>
        <td>{{INPUT(value="提交",type="submit")}}</td>
    </tr>
</table>

8.导入宏

{% import 'forms.html' as forms %}
{% from 'forms.html' import input as input_field, textarea %}

如果你想要导入一个需要访问当前上下文变量的宏,有两种可能的方法:
A.显式地传入请求或请求对象的属性作为宏的参数。
B.与上下文一起(with context)导入宏。

{% from '_helpers.html' import my_macro with context %}

9.include语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码copy到另外一个模板的指定位置

{% include 'header.html' %}
    主体内容
{% include 'footer.html' %}

10.set语句:在模版中,可以使用set语句来定义全局变量。

{% set username='你好' %}
<p>用户名:{{ username }}</p>

11.with语句:定义局部变量。

{% with classroom = '你好' %}
<p>班级:{{ classroom }}</p>
{% endwith %}
{% with %}
    {% set classroom = '你好' %}
    <p>班级:{{ classroom }}</p>
{% endwith %}

12.继承:默认情况下,子模板如果实现了父模版定义的block。那么子模板block中的代码就会覆盖掉父模板中的代码。如果想要在子模板中仍然保持父模板中的代码,那么可以使用{{ super() }}来实现。

{% block body_block %}
        <p style="background: red;">这是父模板中的代码</p>
    {% endblock %}
{% block body_block %}
    {{ super() }}
    <p style="background: green;">我是子模板中的代码</p>
{% endblock %}

如果想要在模版中使用其他模版中的其他代码。那么可以通过{{ self.其他block名字() }}就可以了。

{% block title %}
    首页
{% endblock %}

{% block body_block %}
    {{ self.title() }}
    <p style="background: green;">我是子模板中的代码</p>
{% endblock %}

1.3.2豆瓣列表页

Flask学习笔记(二)_第1张图片

1.3.3视图知识点

1.添加url与视图函数的映射: app.add_url_rule(rule,endpoint=None,view_func=None)。如果没有填写endpoint,那么默认会使用view_func的名字作为endpoint。以后在使用url_for的时候,就要看在映射的时候有没有传递endpoint参数,如果传递了,那么就应该使用endpoint指定的字符串,如果没有传递,那么就应该使用view_func的名字。 @app.route(rule,**options)装饰器:这个装饰器底层,其实也是使用add_url_rule来实现url与视图函数映射的。
2.类视图:支持继承,但是不能跟函数视图一样,写完类视图还需要通过app.add_url_rule(url_rule,view_func)来进行注册。必须继承自flask.views.View.,必须实现dipatch_request方法,以后请求过来后,都会执行这个方法。
标准类视图:

from flask import views,jsonify
class JSONView(views.View):#父类,格式化输出为json格式
    def get_data(self):
        raise NotImplementedError
    def dispatch_request(self):
        return jsonify(self.get_data())
    
class lei_view(JSONView):#子类
    def get_data(self):
        return {"username":"xiaoming","password":"123"}
    
app.add_url_rule("/lei/",endpoint="lei",view_func=lei_view.as_view('lei'))

基于请求方法的类视图:继承于views.MethodView。

class LoginView(views.MethodView):
    def get(self,error=None):
        return render_template("macro.html",error=error)
    def post(self):
        username=request.form.get('username')
        password=request.form.get('password')
        print(username,password)
        if username=="xiaoming" and password=="123":
            return  "登陆成功"
        else:
            return self.get(error="用户名或密码错误")
app.add_url_rule("/mylogin/",view_func=LoginView.as_view('my_login'))

3 类视图中的装饰器
函数视图:自己定义的装饰器必须放在app.route下面。否则这个装饰器就起不到任何作用。
类视图:类内定义类属性decorators,这个类属性是一个列表或者元组都可以,里面装的就是所有的装饰器。
4.蓝图

from flask import Blueprint
    user_bp = Blueprint('user',__name__)
from blueprints.user import user_bp
    app.regist_blueprint(user_bp)```
#如果想要某个蓝图下的所有url都有一个url前缀,那么可以在定义蓝图的时候,指定url_prefix参数
user_bp = Blueprint('user',__name__,url_prefix='/user/')

A.在定义url_prefix的时候,要注意后面的斜杠,如果给了,那么以后在定义url与视图函数的时候,就不要再在url前面加斜杠了。
B.如果项目中的templates文件夹中有相应的模版文件,就直接使用了。如果没有,那么就到在定义蓝图的时候指定的路径中寻找。并且蓝图中指定的路径可以为相对路径,相对的是当前这个蓝图文件所在的目录。

news_bp = Blueprint('news',__name__,url_prefix='/news',template_folder='xxx')

C.蓝图中静态文件的查找规则:
* 在模版文件中,加载静态文件,如果使用url_for(‘static’),那么就只会在app指定的静态文件夹目录下查找静态文件。
* 如果在加载静态文件的时候,指定的蓝图的名字,比如news.static,那么就会到这个蓝图指定的static_folder下查找静态文件(相对于这个蓝图位置的地址)。

news_bp = Blueprint('news',__name__,url_prefix='/news',template_folder='xxx',static_folder='blue_static')

D.url_for反转蓝图中的视图函数为url:
* 如果使用蓝图,那么以后想要反转蓝图中的视图函数为url,那么就应该在使用url_for的时候指定这个蓝图。比如news.news_list。否则就找不到这个endpoint。在模版中的url_for同样也是要满足这个条件,就是指定蓝图的名字。
* 即使在同一个蓝图中反转视图函数,也要指定蓝图的名字。
E.蓝图实现子域名:
需要在主app文件中,需要配置app.config的SERVER_NAME参数。app.config['SERVER_NAME'] = 'jd.com:5000'
在创建蓝图对象的时候,需要传递一个subdomain参数,来指定这个子域名的前缀。例如:
cms_bp = Blueprint('cms',__name__,subdomain='ccc')
C:\Windows\System32\drivers\etc下,找到hosts文件,然后添加域名与本机的映射。
域名和子域名都需要做映射。

127.0.0.1 jd.com
127.0.0.1 ccc.jd.com

1.4数据库

1.4.1SQLAlchemy

1.4.1.1使用SQLAlchemy去连接数据库

1.使用一些配置信息,然后将他们组合成满足条件的字符串

from sqlalchemy import create_engine,text
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'lessons'
USERNAME = 'root'
PASSWORD = 'xxx'
# dialect+driver://username:password@host:port/database
DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

2.然后使用create_engine创建一个引擎engine,然后再调用这个引擎的connect方法,就可以得到这个对象,然后就可以通过这个对象对数据库进行操作了

engine = create_engine(DB_URI)
conn = engine.connect()

3.判断是否连接成功

result = conn.execute(text("select 1"))
print(result.fetchone())

1.4.1.2将ORM模型映射到数据库中

Object Relationship Mapping(对象模型与数据库表的映射)
1.用declarative_base创建一个ORM基类(base)

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

2.用这个Base类作为基类来写自己的ORM类。

class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(50))
    age = Column(Integer)
    country = Column(String(50))

3.使用Base.metadata.create_all()根据engine来将模型映射到数据库中。一旦使用Base.metadata.create_all()将模型映射到数据库中后,即使改变了模型的字段,也不会重新映射了。

Base.metadata.create_all(engine)

1.4.1.3用session做数据的增删改查操作

1.构建session对象:所有数据库的ORM操作都必须通过一个叫做session的会话对象来实现.

from sqlalchemy.orm import sessionmaker
session = sessionmaker(engine)()

2.增

def add_data():
    p1 = Person(name='zhiliao1',age=19,country='china')
    p2 = Person(name='zhiliao2',age=20,country='china')
    session.add_all([p1,p2])
    session.commit()

3.删

def delete_data():
    person = session.query(Person).first()
    session.delete(person)
    session.commit()

4.改

def update_data():
    person = session.query(Person).first()
    person.name = 'ketang'
    session.commit()

5.查

def search_data():
    person = session.query(Person).first()
    print(person)

1.4.1.4SQLAlchemy常用数据类型

  1. Integer:整形,映射到数据库中是int类型。
  2. Float:浮点类型,映射到数据库中是float类型。他占据的32位。
  3. Double:双精度浮点类型,映射到数据库中是double类型,占据64位。
  4. String:可变字符类型,映射到数据库中是varchar类型.
  5. Boolean:布尔类型,映射到数据库中的是tinyint类型。
  6. DECIMAL:定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。
  7. Enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。
  8. Date:存储时间,只能存储年月日。映射到数据库中是date类型。在Python代码中,可以使用datetime.date来指定。
  9. DateTime:存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是datetime类型。
  10. Time:存储时间,可以存储时分秒。映射到数据库中也是time类型。在Python代码中,可以使用datetime.time
  11. Text:存储长字符串。一般可以存储6W多个字符。如果超出了这个范围,可以使用LONGTEXT类型。映射到数据库中就是text类型。
  12. LONGTEXT:长文本类型,映射到数据库中是longtext类型。

1.4.1.5Column常用参数

  1. primary_key:设置某个字段为主键。
  2. autoincrement:设置这个字段为自动增长的。
  3. default:设置某个字段的默认值。在发表时间这些字段上面经常用。
  4. nullable:指定某个字段是否为空。默认值是True,就是可以为空。
  5. unique:指定某个字段的值是否唯一。默认是False。
  6. onupdate:在数据更新的时候会调用这个参数指定的值或者函数。在第一次插入这条数据的时候,不会用onupdate的值,只会使用default的值。常用的就是update_time(每次更新数据的时候都要更新的值)。
  7. name:指定ORM模型中某个属性映射到表中的字段名。如果不指定,那么会使用这个属性的名字来作为字段名。如果指定了,就会使用指定的这个值作为参数。这个参数也可以当作位置参数,在第1个参数来指定。

1.4.1.6query可用参数:

  1. 模型对象。指定查找这个模型中所有的对象。
  2. 模型中的属性。可以指定只查找某个模型的其中几个属性。
  3. 聚合函数。
    • func.count:统计行的数量。
    • func.avg:求平均值。
    • func.max:求最大值。
    • func.min:求最小值。
    • func.sum:求和。
      func上,其实没有任何聚合函数。但是因为他底层做了一些魔术,只要mysql中有的聚合函数,都可以通过func调用。

1.4.1.7filter过滤条件

  1. equals:
  article = session.query(Article).filter(Article.title == "title0").first()
  1. not equals:

    query.filter(User.name != 'ed')
    
  2. like:(ilike)不区分大小写

    query.filter(User.name.like('%ed%'))
    
  3. in_:

    query.filter(User.name.in_(['ed','wendy','jack']))
    # 同时,in也可以作用于一个Query
    query.filter(User.name.in_(session.query(User.name).filter(User.name.like('%ed%'))))
    
  4. not in:

    query.filter(~User.name.in_(['ed','wendy','jack']))
    
  5. is null:

    query.filter(User.name==None)
    # 或者是
    query.filter(User.name.is_(None))
    
  6. is not null:

    query.filter(User.name != None)
    # 或者是
    query.filter(User.name.isnot(None))
    
  7. and_:

    from sqlalchemy import and_
    query.filter(and_(User.name=='ed',User.fullname=='Ed Jones'))
    # 或者是传递多个参数
    query.filter(User.name=='ed',User.fullname=='Ed Jones')
    # 或者是通过多次filter操作
    query.filter(User.name=='ed').filter(User.fullname=='Ed Jones')
    
  8. or:

    from sqlalchemy import or_  query.filter(or_(User.name=='ed',User.name=='wendy'))
    

如果想要查看orm底层转换的sql语句,可以在filter方法后面不要再执行任何方法直接打印就可以看到了

1.4.1.8外键

从表中外键的字段,必须和父表的主键字段类型保持一致
外键约束有以下几项(ondelete):

  1. RESTRICT:父表数据被删除,会阻止删除。默认就是这一项。
  2. NO ACTION:在MySQL中,同RESTRICT。
  3. CASCADE:级联删除。
  4. SET NULL:父表数据被删除,子表数据会设置为NULL。

1.4.1.9ORM关系以及一对多

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)

    # articles = relationship("Article")

    def __repr__(self):
        return "" % self.username

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text,nullable=False)
    uid = Column(Integer,ForeignKey("user.id"))

    author = relationship("User",backref="articles")

另外,可以通过backref来指定反向访问的属性名称。articles是有多个。他们之间的关系是一个一对多的关系。

1.4.1.10ORM关系以及一对一

如果想要将两个模型映射成一对一的关系,那么应该在父模型中,指定引用的时候,要传递一个uselist=False这个参数进去。就是告诉父模型,以后引用这个从模型的时候,不再是一个列表了,而是一个对象了。

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)

    extend = relationship("UserExtend",uselist=False)

    def __repr__(self):
        return "" % self.username

class UserExtend(Base):
    __tablename__ = 'user_extend'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))
    uid = Column(Integer,ForeignKey("user.id"))

    user = relationship("User",backref="extend")

当然,也可以借助sqlalchemy.orm.backref来简化代码:

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)

    # extend = relationship("UserExtend",uselist=False)

    def __repr__(self):
        return "" % self.username

class UserExtend(Base):
    __tablename__ = 'user_extend'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))
    uid = Column(Integer,ForeignKey("user.id"))

    user = relationship("User",backref=backref("extend",uselist=False))

1.4.1.11ORM关系以及多对多

多对多的关系需要通过一张中间表来绑定他们之间的关系。在两个需要做多对多的模型中随便选择一个模型,定义一个relationship属性,来绑定三者之间的关系,在使用relationship的时候,需要传入一个secondary=中间表。

  1. 先把两个需要做多对多的模型定义出来
class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)

    # tags = relationship("Tag",backref="articles",secondary=article_tag)

    def __repr__(self):
        return "" % self.title


class Tag(Base):
    __tablename__ = 'tag'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)

    articles = relationship("Article",backref="tags",secondary=article_tag)

    def __repr__(self):
        return "" % self.name
  1. 使用Table定义一个中间表,中间表一般就是包含两个模型的外键字段就可以了,并且让他们两个来作为一个“复合主键”。
article_tag = Table(
    "article_tag",
    Base.metadata,
    Column("article_id",Integer,ForeignKey("article.id"),primary_key=True),
    Column("tag_id",Integer,ForeignKey("tag.id"),primary_key=True)
)

1.4.1.12ORM层面的删除数据:

ORM层面删除数据,会无视mysql级别的外键约束。直接会将对应的数据删除,然后将从表中的那个外键设置为NULL。如果想要避免这种行为,应该将从表中的外键的nullable=False
在SQLAlchemy,只要将一个数据添加到session中,和他相关联的数据都可以一起存入到数据库中了。这些是怎么设置的呢?其实是通过relationship的时候,有一个关键字参数cascade可以设置这些属性:

  1. save-update:默认选项。在添加一条数据的时候,会把其他和他相关联的数据都添加到数据库中。这种行为就是save-update属性影响的。
  2. delete:表示当删除某一个模型中的数据的时候,是否也删掉使用relationship和他关联的数据。
  3. delete-orphan:表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉。当然如果父表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,不能用在多对多以及多对一上。并且还需要在子模型中的relationship中,增加一个single_parent=True的参数。
  4. merge:默认选项。当在使用session.merge,合并一个对象的时候,会将使用了relationship相关联的对象也进行merge操作。
  5. expunge:移除操作的时候,会将相关联的对象也进行移除。这个操作只是从session中移除,并不会真正的从数据库中删除。
  6. all:是对save-update, merge, refresh-expire, expunge, delete几种的缩写。

1.4.1.13排序:

  1. order_by:可以指定根据这个表中的某个字段进行排序,如果在前面加了一个-,代表的是降序排序。

  2. 在模型定义的时候指定默认排序:有些时候,不想每次在查询的时候都指定排序的方式,可以在定义模型的时候就指定排序的方式。有以下两种方式:

    • relationship的order_by参数:在指定relationship的时候,传递order_by参数来指定排序的字段。
    • 在模型定义中,添加以下代码:

    mapper_args = {
    “order_by”: title
    }
    即可让文章使用标题来进行排序。

  3. 正序排序与倒序排序:默认是使用正序排序。如果需要使用倒序排序,那么可以使用这个字段的desc()方法,或者是在排序的时候使用这个字段的字符串名字,然后在前面加一个负号。

1.4.1.14limit、offset和切片操作:

  1. limit:可以限制每次查询的时候只查询几条数据。
  2. offset:可以限制查找数据的时候过滤掉前面多少条。
  3. 切片:可以对Query对象使用切片操作,来获取想要的数据。可以使用slice(start,stop)方法来做切片操作。也可以使用[start:stop]的方式来进行切片操作。一般在实际开发中,中括号的形式是用得比较多的。希望大家一定要掌握。示例代码如下:
articles = session.query(Article).order_by(Article.id.desc())[0:10]

1.4.1.15懒加载

在一对多,或者多对多的时候,如果想要获取多的这一部分的数据的时候,往往能通过一个属性就可以全部获取了。比如有一个作者,想要或者这个作者的所有文章,那么可以通过user.articles就可以获取所有的。但有时候我们不想获取所有的数据,比如只想获取这个作者今天发表的文章,那么这时候我们可以给relationship传递一个lazy=‘dynamic’,以后通过user.articles获取到的就不是一个列表,而是一个AppenderQuery对象了。这样就可以对这个对象再进行一层过滤和排序等操作。
通过lazy='dynamic',获取出来的多的那一部分的数据,就是一个AppenderQuery对象了。这种对象既可以添加新数据,也可以跟Query一样,可以再进行一层过滤。
总而言之一句话:如果你在获取数据的时候,想要对多的那一边的数据再进行一层过滤,那么这时候就可以考虑使用lazy='dynamic'
lazy可用的选项:

  1. select:这个是默认选项。还是拿user.articles的例子来讲。如果你没有访问user.articles这个属性,那么sqlalchemy就不会从数据库中查找文章。一旦你访问了这个属性,那么sqlalchemy就会立马从数据库中查找所有的文章,并把查找出来的数据组装成一个列表返回。这也是懒加载。
  2. dynamic:这个就是我们刚刚讲的。就是在访问user.articles的时候返回回来的不是一个列表,而是AppenderQuery对象。
    group_by:
    根据某个字段进行分组。比如想要根据性别进行分组,来统计每个分组分别有多少人,那么可以使用以下代码来完成:
session.query(User.gender,func.count(User.id)).group_by(User.gender).all()

1.4.1.16having:

having是对查找结果进一步过滤。比如只想要看未成年人的数量,那么可以首先对年龄进行分组统计人数,然后再对分组进行having过滤。示例代码如下:

result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age >= 18).all()

1.4.1.17 join:

  1. join分为left join(左外连接)和right join(右外连接)以及内连接(等值连接)。
  2. 参考的网页:http://www.jb51.net/article/15386.htm
  3. 在sqlalchemy中,使用join来完成内连接。在写join的时候,如果不写join的条件,那么默认将使用外键来作为条件连接。
  4. query查找出来什么值,不会取决于join后面的东西,而是取决于query方法中传了什么参数。就跟原生sql中的select 后面那一个一样。
    比如现在要实现一个功能,要查找所有用户,按照发表文章的数量来进行排序。示例代码如下:
result = session.query(User,func.count(Article.id)).join(Article).group_by(User.id).order_by(func.count(Article.id).desc()).all()

1.4.1.18subquery

子查询可以让多个查询变成一个查询,只要查找一次数据库,性能相对来讲更加高效一点。不用写多个sql语句就可以实现一些复杂的查询。那么在sqlalchemy中,要实现一个子查询,应该使用以下几个步骤:

  1. 将子查询按照传统的方式写好查询代码,然后在query对象后面执行subquery方法,将这个查询变成一个子查询。
  2. 在子查询中,将以后需要用到的字段通过label方法,取个别名。
  3. 在父查询中,如果想要使用子查询的字段,那么可以通过子查询的返回值上的c属性拿到。
    整体的示例代码如下:
stmt = session.query(User.city.label("city"),User.age.label("age")).filter(User.username=='李A').subquery()
result = session.query(User).filter(User.city==stmt.c.city,User.age==stmt.c.age).all()

1.4.2Flask_SQLAlchemy

1.4.2.1flask-sqlalchemy

pip install flask-sqlalchemy

1.链接数据库

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
HOSTNAME='127.0.0.1'
PORT='3306'
DATABASE='lessons_flask_sqlalchemy'
USERNAME='root'
PASSWORD='xxx'
DB_URI="mysql+pymysql://{username}:{password}@{host}:{port}/{database}?charset=utf8mb4".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,database=DATABASE)
app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI']=DB_URI
db=SQLAlchemy(app)

2.创建表类,并映射到数据库中

class User(db.Model):
    __tablename__='user'#默认类名小写后的名字
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    username=db.Column(db.String(50),nullable=False)
class Article(db.Model):
    __tablename__='article'
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    title=db.Column(db.String(50),nullable=False)
    uid=db.Column(db.Integer,db.ForeignKey("user.id"))
    author=db.relationship("User",backref="articles")
with app.app_context():#在试图函数里运行时就不要,在试图函数外运行时需要
    db.drop_all()
    db.create_all()

3.利用session会话将数据添加到数据表中。

user=User(username="张三")
article=Article(title='title1')
article.author=user
with app.app_context():#在试图函数里运行时就不要,在试图函数外运行时需要
    db.session.add(article)
    db.session.commit()

4.查

with app.app_context():
    users=User.query.all()#4.查询
    print(users)

5.改

with app.app_context():
    user=User.query.filter(User.username=="张三").first()
    user.username="王五"
    db.session.commit()

6.删

with app.app_context():
    user=User.query.filter(User.username=="王五").first()
    db.session.delete(user)
    db.session.commit()

1.4.2.2alembic

用来做orm模型与数据库的迁移和映射

pip install alembic

1.sqlalchemy下使用alembic

from sqlalchemy import Column,String,Integer,create_engine
from sqlalchemy.ext.declarative import declarative_base
HOSTNAME='127.0.0.1'
PORT='3306'
DATABASE='alembic1'
USERNAME='root'
PASSWORD='xxx'
DB_URI="mysql+pymysql://{username}:{password}@{host}:{port}/{database}?charset=utf8mb4".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,database=DATABASE)
engine=create_engine(DB_URI)
Base=declarative_base()
class User(Base):
    __tablename__="user"
    id=Column(Integer,primary_key=True,autoincrement=True)
    username=Column(String(50),nullable=False)
Base.metadata.create_all()

在工程文件夹下初始化仓库:

alembic init alembic

修改alembic.ini文件:

sqlalchemy.url = mysql+pymysql://root:xxx@localhost/alembic1

修改env.py文件:

import sys,os
sys.path.append(os.path.dirname(os.path.dirname(__file__))+"/ALCH/")#orm定义文件alembic_sql.py的文件夹地址
from  alembic_sql import Base#这是我的ORM的py文件
...
target_metadata=Base.metadata#创建设置模型的元类

orm生成迁移脚本

alembic revision --autogenerate -m "my_first_commit"

迁移脚本映射到数据库

alembic upgrade head

命令和参数解释:

init:创建一个alembic仓库。
revision:创建一个新的版本文件。
–autogenerate:自动将当前模型的修改,生成迁移脚本。
-m:本次迁移做了哪些修改,用户可以指定这个参数,方便回顾。
upgrade:将指定版本的迁移文件映射到数据库中,会执行版本文件中的upgrade函数。如果有多个迁移脚本没有被映射到数据库中,那么会执行多个迁移脚本。
[head]:代表最新的迁移脚本的版本号。
downgrade:会执行指定版本的迁移文件中的downgrade函数。
heads:展示head指向的脚本文件版本号。
history:列出所有的迁移版本及其信息。
current:展示当前数据库中的版本号。

错误描述 原因 解决办法
FAILED: Target database is not up to date. 主要是heads和current不相同。current落后于heads的版本。 将current移动到head上。alembic upgrade head
FAILED: Can’t locate revision identified by ‘77525ee61b5b’ 数据库中存的版本号不在迁移脚本文件中 删除数据库的alembic_version表中的数据,重新执行alembic upgrade head

2.flask-sqlalchemyi下使用alembic

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
HOSTNAME='127.0.0.1'
PORT='3306'
DATABASE='alem3'
USERNAME='root'
PASSWORD='xxx'
DB_URI="mysql+pymysql://{username}:{password}@{host}:{port}/{database}?charset=utf8mb4".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,database=DATABASE)
app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI']=DB_URI
db=SQLAlchemy(app)
class User(db.Model):
    __tablename__='user'
    id=db.Column(db.Integer,primary_key=True,autoincrement=True)
    username=db.Column(db.String(50),nullable=False)
@app.route("/")
def hello():
    return "hello"
if __name__=="__main__":
    app.run()

在工程文件夹下初始化仓库:

alembic init alembic

修改alembic.ini文件:

sqlalchemy.url = mysql+pymysql://root:xxx@localhost/alem3

修改env.py文件:

import sys,os
sys.path.append(os.path.dirname(os.path.dirname(__file__))+"/ALCH/")#orm定义文件alembic_sql.py的文件夹地址
import fla_sql_aql #这是我的ORM的py文件
...
target_metadata=fla_sql_aql.db.Model.metadata#创建设置模型的元类

orm生成迁移脚本

alembic revision --autogenerate -m "my_first_commit"

迁移脚本映射到数据库

alembic upgrade head

1.4.2.3flask-script

Flask-Script的作用是可以通过命令行的形式来操作Flask。例如通过命令跑一个开发版本的服务器、设置数据库,定时任务等。要使用Flask-Script,可以通过pip install flask-script安装最新版本。(这里降低了flask的版本pip3 install flask==1.1.2)

@manager.command
def hello():#1.自定义command
    print("你好")
@manager.option("-u","--username",dest="username")
@manager.option("-a","--age",dest="age")
def u_a(username,age):#2.定义传参command
    print("当前用户是:%s,年龄是:%s"%(username,age))
from db_script import db_manager
manager.add_command("db",db_manager)

1.4.2.3flask-migrate

from flask_script import Manager
from f_m import app
from exts import db
from flask_migrate import Migrate,MigrateCommand
from models import User
manager=Manager(app)
Migrate(app,db)
manager.add_command("db",MigrateCommand)

if __name__=="__main__":
    manager.run()

1.5知识点补充

1.5.1Flask-WTF

Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:CSRF保护,文件上传等。安装Flask-WTF默认也会安装WTForms.

pip install flask-wtf

1.5.1.1表单验证

from flask import Flask,request,render_template
from wtforms import Form,StringField
from wtforms.validators import Length,EqualTo
class RegisterForm(Form):
    username=StringField(validators=[Length(min=3,max=10,message="用户名要3-10位长")])
    password=StringField(validators=[Length(min=6,max=10)])
    password_repeat=StringField(validators=[Length(min=6,max=10),EqualTo("password")])
app=Flask(__name__)
@app.route("/reg/",methods=['GET','POST'])
def reg():
    if request.method=='GET':
        return render_template('regist.html')
    else:
        form=RegisterForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)
            return "fail"
if __name__=="__main__":
    app.run(debug=True)

常用的验证器:
数据发送过来,经过表单验证,因此需要验证器来进行验证,以下对一些常用的内置验证器进行讲解:

验证器 功能
Email 验证上传的数据是否为邮箱。
EqualTo 验证上传的数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等。
InputRequired 原始数据的需要验证。如果不是特殊情况,应该使用InputRequired。
Length 长度限制,有min和max两个值进行限制。
NumberRange 数字的区间,有min和max两个值限制,如果处在这两个数字之间则满足。
Regexp 自定义正则表达式。
URL 必须要是URL的形式。
UUID 验证UUID。

自定义验证器:

class IndexForm(Form):
    captcha=StringField(validators=[Length(6,6)])
    def validate_captcha(self,field):#自定义函数(validate_)field=captcha
        if field.data!="123456":
            raise ValidationError("验证码错误")

1.5.1.2模版渲染

class SettingsForm(Form):
    username=StringField("用户名",validators=[InputRequired()])
@app.route("/settings/",methods=['GET','POST'])
def settings():
    if request.method=='GET':
        form =SettingsForm()
        return render_template("settings.html",form=form)
    else:
        form=SettingsForm(request.form)

<tr>
	<td>{{form.username.label}}</td>
	<td>{{form.username()}}</td>
</tr>

1.5.1.3文件上传

1.上传文件

import os
from werkzeug.utils import secure_filename
UPLOAD_PATH=os.path.join(os.path.dirname(__file__),'images')
@app.route("/upload/",methods=['GET','POST'])
def upload():
    if request.method=='GET':
        return render_template("upload.html")
    else:
        desc=request.form.get("desc")#获取描述信息
        avatar=request.files.get("avatar")#获取文件
        filename=secure_filename(avatar.filename)#上传文件的名字做安全处理
        avatar.save(os.path.join(UPLOAD_PATH,filename))#保存文件
        print(desc)
        return "文件上传成功"
<html>
    <head>
        <meta charset="UTF-8">
        <title>上传文件</title>
    </head>
    <body>
        <form action="" method="post" enctype="multipart/form-data">
            <table>
                <tbody>
                    <tr>
                        <td>头像:</td>
                        <td><input type="file" name="avatar"></td>
                    </tr>
                    <tr>
                        <td>描述:</td>
                        <td><input type="text" name="desc"></td>
                    </tr>
                    <tr>
                        <td></td>
                        <td><input type="submit" value="提交"></td>
                    </tr>
                </tbody>
            </table>
        </form>
    </body>
</html>

2.返回文件

from flask import end_from_directory
@app.route("/images//")
def get_image(filename):
    return send_from_directory(UPLOAD_PATH,filename)

3.上传文件验证

from wtforms import Form,FileField,StringField
from wtforms.validators import InputRequired
from flask_wtf.file import FileAllowed,FileRequired
class UploadForm(Form):
    avatar=FileField(validators=[FileRequired(),FileAllowed(['jpg','png','gif'])])
    desc=StringField(validators=[InputRequired()])
    
from forms import UploadForm
from werkzeug.datastructures import CombinedMultiDict
@app.route("/upload/",methods=['GET','POST'])
def upload():
    if request.method=='GET':
        return render_template("upload.html")
    else:
        form=UploadForm(CombinedMultiDict([request.form,request.files]))
        if form.validate():
            desc=form.desc.data获取描述信息
            avatar=form.avatar.data#获取文件
            filename=secure_filename(avatar.filename)#上传文件的名字做安全处理
            avatar.save(os.path.join(UPLOAD_PATH,filename))#保存文件
            print(desc)
            return "文件上传成功"
        else:
            print(form.errors)
            return "fail"

1.5.2cookie和session

1.5.2.1cookie

1.设置cookie,在response对象上使用set-cookie方法。

@app.route("/")
def coo():
    resp=Response("hello")
    resp.set_cookie("username","zoe")#键,值
    return resp

2.删除cookie,在response对象上使用delete_cookie方法。

@app.route('/dele/')
def dele():
    resp=Response("done")
    resp.delete_cookie('username')#键
    return resp

3.设置cookie的有效期
默认是浏览会话结束,即浏览器关闭后cookie失效。

resp.set_cookie("username","zoe",max_age=60)#max_age(有效期多少秒)
from datetime import datetime,timedelta
 	due=datetime.now()+timedelta(days=30,hours=16)#使用格林尼治时间,即比北京时间少8小时
    resp.set_cookie("username","zoe",expires=due)#使用expires设置截止时间

4.设置cookie的有效域名

resp.set_cookie("username","zoe",domain='.my.com')#y.com下的所有子域名都可用此cookie
from flask import Blueprint,request
bp=Blueprint('cook',__name__,subdomain='cook')
@bp.route('/')
def index():
    username=request.cookies.get('username')
    return username or "没有获取到cookie"
from  cookie_view import bp 
app=Flask(__name__)
app.register_blueprint(bp)
app.config['SERVER_NAME']='my.com:5000'

设置host的域名和端口映射
Flask学习笔记(二)_第2张图片

1.5.2.2session

1.设置session

from flask import Flask,session
import os
from datetime import timedelta
app=Flask(__name__)
app.config['SECRET_KEY']=os.urandom(24)#对session内容进行加密,然后放到cookie中。
@app.route('/')
def index():
    session['username']='zoe'
    return 'hello'

2.获取session

@app.route('/get_session/')
def get_session():
    username=session.get('username')
    return username or '没有拿到session'

3.删除session

@app.route('/del_session/')
def del_session():
    # session.pop['username']
    session.clear()#删除session中的所有信息
    return '删除成功'

4…设置session有效期,默认是When the browsing session ends

app.config['PERMANENT_SESSION_LIFETIME']=timedelta(days=2)#比31天延长多久
@app.route('/set_session/')
def set_session():
    session['username']='zoe'
    session.permanent=True#默认保留31天,设置app.config['PSERMANENT_SESSION_LIFETIME']后按延长时间
    return 'hello'

1.5.3CSRF攻击与防御

1.CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,如果你访问了一个别有用心或病毒网站,这个网站可以在网页源代码中插入js代码,使用js代码给其他服务器发送请求(比如ICBC的转账请求)。那么因为在发送请求的时候,浏览器会自动的把cookie发送给对应的服务器,这时候相应的服务器(比如ICBC网站),就不知道这个请求是伪造的,就被欺骗过去了。从而达到在用户不知情的情况下,给某个服务器发送了一个请求(比如转账)。

from flask_wtf import CSRFProtect
CSRFProtect(app)
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">

2.Ajax处理csrf

	'_ajaxSetup': function() {
		$.ajaxSetup({
			'beforeSend':function(xhr,settings) {
				if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                    var csrftoken = $('meta[name=csrf-token]').attr('content');
                    xhr.setRequestHeader("X-CSRFToken", csrftoken)
                }
			}
		});
	}

1.5.4Flask上下文

1.单线程

from threading import Thread
request='123'
class Mythread(Thread):
    def run(self):
        global request
        request='abc'
        print("子线程:",request)#子线程: abc
mythread=Mythread()
mythread.start()
mythread.join()
print("主线程:",request)#主线程: abc

2.绑定在Local对象上的属性,在每个线程中都是隔离的

from threading import Thread
from werkzeug.local import Local
local=Local()
local.request='123'
class Mythread(Thread):
    def run(self):
        local.request='abc'
        print("子线程:",local.request)#子线程: abc
mythread=Mythread()
mythread.start()
mythread.join()
print("主线程:",local.request)#主线程: 123

3.应用上下文

with app.app_context():
    print(current_app.name)#context_flask

4.请求上下文

with app.test_request_context():
    print(url_for('m_l'))

5.g对象
g全局使用,多线程。

1.5.5钩子函数

before_first_request:处理第一次请求之前执行。例如以下代码:

  @app.before_first_request
  def first_request():
      print 'first time request'

before_request:在每次请求之前执行。通常可以用这个装饰器来给视图函数增加一些变量。例如以下代码:

  @app.before_request
  def before_request():
      if not hasattr(g,'user'):
          setattr(g,'user','xxxx')

teardown_appcontext:不管是否有异常,注册的函数都会在每次请求之后执行。

  @app.teardown_appcontext
  def teardown(exc=None):
      if exc is None:
          db.session.commit()
      else:
          db.session.rollback()
      db.session.remove()

template_filter:在使用Jinja2模板的时候自定义过滤器。比如可以增加一个upper的过滤器(当然Jinja2已经存在这个过滤器,本示例只是为了演示作用):

@app.template_filter
  def upper_filter(s):
      return s.upper()

context_processor:上下文处理器。返回的字典中的键可以在模板上下文中使用。必须返回一个字典,无参数就返回{}空字典,例如:

  @app.context_processor
  return {'current_user':'xxx'}

errorhandler:errorhandler接收状态码,可以自定义返回这种状态码的响应的处理方法。例如:

  @app.errorhandler(404)
  def page_not_found(error):
      return 'This page does not exist',404

1.5.6flask信号

flask中的信号使用的是一个第三方插件,叫做blinker。
1.自定义信号
2.内置信号

flask.template_rendered:模版渲染完毕后发送,示例如下:

    from flask import template_rendered
    def log_template_renders(sender,template,context,*args):
        print 'sender:',sender
        print 'template:',template
        print 'context:',context

    template_rendered.connect(log_template_renders,app)

flask.request_started:请求开始之前,在到达视图函数之前发送,订阅者可以调用request之类的标准全局代理访问请求。示例如下:

def log_request_started(sender,**extra):
    print 'sender:',sender
    print 'extra:',extra
request_started.connect(log_request_started,app)

flask.request_finished:请求结束时,在响应发送给客户端之前发送,可以传递response,示例代码如下:

def log_request_finished(sender,response,*args):
    print 'response:',response
request_finished.connect(log_request_finished,app)

flask.got_request_exception:在请求过程中抛出异常时发送,异常本身会通过exception传递到订阅的函数。示例代码如下:

def log_exception_finished(sender,exception,*args):
    print 'sender:',sender
    print type(exception)
got_request_exception.connect(log_exception_finished,app)

flask.request_tearing_down:请求被销毁的时候发送,即使在请求过程中发生异常,也会发送,示例代码如下:

def log_request_tearing_down(sender,**kwargs):
    print 'coming...'
request_tearing_down.connect(log_request_tearing_down,app)

flask.appcontext_tearing_down:在应用上下文销毁的时候发送,它总是会被调用,即使发生异常。示例代码如下:

def log_appcontext_tearing_down(sender,**kwargs):
    print 'coming...'
appcontext_tearing_down.connect(log_appcontext_tearing_down,app)

1.5.7Flask-Restful插件

pip install flask-restful

1.定义Restful的视图
如果使用Flask-Restful,那么定义视图函数的时候,就要继承自flask_restful.Resource类,然后再根据当前请求的method来定义相应的方法。比如期望客户端是使用get方法发送过来的请求,那么就定义一个get方法。类似于MethodView。

from flask import Flask
from flask_restful import Api,Resource
app = Flask(__name__)
# 用Api来绑定app
api = Api(app)
class IndexView(Resource):
    def post(self):
        return {"username":"zoe"}
api.add_resource(IndexView,'/',endpoint='index')
if __name__ == '__main__':
    app.run(debug=True)

用postman工具来测试:
Flask学习笔记(二)_第3张图片
2.参数解析:Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,叫做reqparse。

1.5.8memcached

1.安装启动:
windows:管理员身份
安装:memcached.exe -d install。
启动:memcached.exe -d start。

可能出现的问题: 提示你没有权限:在打开cmd的时候,右键使用管理员身份运行。
提示缺少pthreadGC2.dll文件:将pthreadGC2.dll文件拷贝到windows/System32.
不要放在含有中文的路径下面。

在这里插入图片描述
-d:这个参数是让memcached在后台运行。
-m:指定占用多少内存。以M为单位,默认为64M。
-p:指定占用的端口。默认端口是11211。
-l:别的机器可以通过哪个ip地址连接到我这台服务器。如果是通过service memcached start的方式,那么只能通过本机连接。如果想要让别的机器连接,就必须设置-l 0.0.0.0。
如果想要使用以上参数来指定一些配置信息,那么不能使用service memcached start,而应该使用/usr/bin/memcached的方式来运行。比如/usr/bin/memcached -u memcache -m 1024 -p 11222 start。
3.telnet操作memcached:

可能出现的问题:‘telnet’ 不是内部或外部命令,也不是可运行的程序或批处理文件。将telnet功能打开。

Flask学习笔记(二)_第4张图片
set和add的区别:add是只负责添加数据,不会去修改数据。如果添加的数据的key已经存在了,则添加失败,如果添加的key不存在,则添加成功。而set不同,如果memcached中不存在相同的key,则进行添加,如果存在,则替换。

set username 0 120 3       #1.set key [0,1](是否压缩) timeout(缓存过期时间) value_length(数据长度)
zoe      #value
STORED
get username       #2.get key:获取此key对应的value值
VALUE username 0 3
zoe
END
add age 0 120 2         #3.add key [0,1](是否压缩) timeout(0=forever) value_length(数据长度)
18
STORED
ci
ERROR
incr age 2         #4.incr key num:给key的value数值加上几    
20
get age
VALUE age 0 2
20
END
decr age 4           #5.decr key num:给key的value数值减去几 
16
get age
VALUE age 0 2
16
END
del
ERROR
delete age                   #6.delete key:删除键值对
DELETED
get age
END
get username
VALUE username 0 3
zoe
END
flush_all               #7.flush_all:删除memcached中的所有数据。
OK
get username
END
stats          #8.stats:查看memcached的当前状态

4.python操作memcached:
安装

pip install python-memcached

连接

 import memcache
 mc = memcache.Client(['127.0.0.1:11211','192.168.174.130:11211'],debug=True)# 在连接之前,一定要切记先启动memcached

操作数据

mc.set('username','hello world',time=60*5)#默认time=0:forever
mc.set_multi({'email':'[email protected]','telphone':'111111'},time=60*5)#set多条键值对
 
mc.get('telphone')

mc.delete('email')

mc.incr('age',delta=10)#默认是1

mc.decr('read_count')

5.memcached的安全性
memcached的操作不需要任何用户名和密码,只需要知道memcached服务器的ip地址和端口号即可。因此memcached使用的时候尤其要注意他的安全性。这里提供两种安全的解决方案。
A.使用-l参数设置为只有本地可以连接:这种方式,就只能通过本机才能连接,别的机器都不能访问,可以达到最好的安全性。
B.使用防火墙,关闭11211端口,外面也不能访问。

ufw enable # 开启防火墙
ufw disable # 关闭防火墙
ufw default deny # 防火墙以禁止的方式打开,默认是关闭那些没有开启的端口
ufw deny 端口号 # 关闭某个端口
ufw allow 端口号 #开启某个端口

1.5.9redis

3.错误

1.Exception: Install ‘email_validator’ for email validation support.
解决办法:降低wtforms的版本

pip install wtforms==2.2.1

参考:https://blog.csdn.net/not_so_bad/article/details/120936176
2.No module named ‘flask._compat’
解决办法:改为

from flask_script._compat import text_type

3.cannot import name ‘MigrateCommand’ from ‘flask_migrate’
解决办法:

pip3 install flask==1.1.2

参考:https://blog.csdn.net/YZL40514131/article/details/122954278

你可能感兴趣的:(job,flask,学习,笔记)