Flask学习笔记(二)
8.(url,code=301)#默认是302
1. 1.3.知识点
1.在渲染模版的时候,默认会从项目根目录下的目录下查找模版。也可以在Flask初始化的时候指定来指定模版的路径。
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。
(value,,=false):如果当前变量没有值,则会使用参数中的值来代替。name|(‘’)——如果name不存在,则会使用来替代。=False默认是在只有这个变量为的时候才会使用中的值,如果想使用的形式判断是否为false,则可以传递=true。也可以使用or来替换。name
or ‘’
(value)或e:转义字符,会将等符号转义成HTML中的符号。例如:|或|e。({% off/on%} XXXXX{%%}关掉或开启局部转义)
first(value):返回一个序列的第一个元素。names|first。
(value,*arags,**):格式化字符串。例如以下代码:{{ “%s” -> “%s”|(‘Hello?’,“Foo!”) }}将输出:? - Foo!
last(value):返回一个序列的最后一个元素。示例:names|last。
(value):返回一个序列或者字典的长度。示例:names|。
join(value,d=u’'):将一个序列用d这个参数的值拼接成字符串。
safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:|safe。
int(value):将值转换为int类型。 float(value):将值转换为float类型。
lower(value):将字符串转换为小写。 upper(value):将字符串转换为小写。
(value,old,new): 替换将old替换为new的字符串。
(value,=255,=False):截取长度的字符串。
(value):删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。
trim:截取字符串前面和后面的空白字符。 (value):将变量转换成字符串。
(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/60return "%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 ,,if),不可以使用和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.
当前迭代的索引(从0开始)
loop.first
是否是第一次迭代,返回True或False
loop.last
是否是最后一次迭代,返回True或False
loop.
序列的长度
用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.宏:模板中的宏跟中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量。
{% 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 )导入宏。
{% from '_helpers.html' import my_macro with context %}
9.语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码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豆瓣列表页
1.3.3视图知识点
1.添加url与视图函数的映射: app.(rule,=None,=None)。如果没有填写,那么默认会使用的名字作为。以后在使用的时候,就要看在映射的时候有没有传递参数,如果传递了,那么就应该使用指定的字符串,如果没有传递,那么就应该使用的名字。 @app.route(rule,**)装饰器:这个装饰器底层,其实也是使用来实现url与视图函数映射的。
2.类视图:支持继承,但是不能跟函数视图一样,写完类视图还需要通过app.(,)来进行注册。必须继承自flask.views.View.,必须实现方法,以后请求过来后,都会执行这个方法。
标准类视图:
from flask import views,jsonify
class JSONView(views.View):#父类,格式化输出为json格式def get_data(self):raise NotImplementedErrordef 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.。
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下面。否则这个装饰器就起不到任何作用。
类视图:类内定义类属性,这个类属性是一个列表或者元组都可以,里面装的就是所有的装饰器。
4.蓝图
from flask import Blueprintuser_bp = Blueprint('user',__name__)
from blueprints.user import user_bpapp.regist_blueprint(user_bp)```
#如果想要某个蓝图下的所有url都有一个url前缀,那么可以在定义蓝图的时候,指定url_prefix参数
user_bp = Blueprint('user',__name__,url_prefix='/user/')
A.在定义的时候,要注意后面的斜杠,如果给了,那么以后在定义url与视图函数的时候,就不要再在url前面加斜杠了。
B.如果项目中的文件夹中有相应的模版文件,就直接使用了。如果没有,那么就到在定义蓝图的时候指定的路径中寻找。并且蓝图中指定的路径可以为相对路径,相对的是当前这个蓝图文件所在的目录。
news_bp = Blueprint('news',__name__,url_prefix='/news',template_folder='xxx')
C.蓝图中静态文件的查找规则:
* 在模版文件中,加载静态文件,如果使用(‘’),那么就只会在app指定的静态文件夹目录下查找静态文件。
* 如果在加载静态文件的时候,指定的蓝图的名字,比如news.,那么就会到这个蓝图指定的下查找静态文件(相对于这个蓝图位置的地址)。
news_bp = Blueprint('news',__name__,url_prefix='/news',template_folder='xxx',static_folder='blue_static')
D.反转蓝图中的视图函数为url:
* 如果使用蓝图,那么以后想要反转蓝图中的视图函数为url,那么就应该在使用的时候指定这个蓝图。比如news.。否则就找不到这个。在模版中的同样也是要满足这个条件,就是指定蓝图的名字。
* 即使在同一个蓝图中反转视图函数,也要指定蓝图的名字。
E.蓝图实现子域名:
需要在主app文件中,需要配置app.的参数。app.[''] = ':5000'
在创建蓝图对象的时候,需要传递一个参数,来指定这个子域名的前缀。例如:
= ('cms',,='ccc')。
在C:\\\\etc下,找到hosts文件,然后添加域名与本机的映射。
域名和子域名都需要做映射。
127.0.0.1
127.0.0.1
1.4数据库 1.4. 1.4.1.1使用去连接数据库
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.然后使用创建一个引擎,然后再调用这个引擎的方法,就可以得到这个对象,然后就可以通过这个对象对数据库进行操作了
engine = create_engine(DB_URI)
conn = engine.connect()
3.判断是否连接成功
result = conn.execute(text("select 1"))
print(result.fetchone())
1.4.1.2将ORM模型映射到数据库中
(对象模型与数据库表的映射)
1.用创建一个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..()根据来将模型映射到数据库中。一旦使用Base..()将模型映射到数据库中后,即使改变了模型的字段,也不会重新映射了。
Base.metadata.create_all(engine)
1.4.1.3用做数据的增删改查操作
1.构建对象:所有数据库的ORM操作都必须通过一个叫做的会话对象来实现.
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.常用数据类型 :整形,映射到数据库中是int类型。Float:浮点类型,映射到数据库中是float类型。他占据的32位。:双精度浮点类型,映射到数据库中是类型,占据64位。:可变字符类型,映射到数据库中是类型.:布尔类型,映射到数据库中的是类型。:定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。Enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。Date:存储时间,只能存储年月日。映射到数据库中是date类型。在代码中,可以使用.date来指定。:存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是类型。Time:存储时间,可以存储时分秒。映射到数据库中也是time类型。在代码中,可以使用.:存储长字符串。
一般可以存储6W多个字符。如果超出了这个范围,可以使用类型。映射到数据库中就是text类型。:长文本类型,映射到数据库中是类型。 1.4.1.常用参数 :设置某个字段为主键。:设置这个字段为自动增长的。:设置某个字段的默认值。在发表时间这些字段上面经常用。:指定某个字段是否为空。默认值是True,就是可以为空。:指定某个字段的值是否唯一。默认是False。:在数据更新的时候会调用这个参数指定的值或者函数。在第一次插入这条数据的时候,不会用的值,只会使用的值。常用的就是(每次更新数据的时候都要更新的值)。name:指定ORM模型中某个属性映射到表中的字段名。如果不指定,那么会使用这个属性的名字来作为字段名。如果指定了,就会使用指定的这个值作为参数。这个参数也可以当作位置参数,在第1个参数来指定。 1.4.1.可用参数: 模型对象。指定查找这个模型中所有的对象。模型中的属性。可以指定只查找某个模型的其中几个属性。聚合函数。 1.4.1.过滤条件 :
article = session.query(Article).filter(Article.title == "title0").first()
not :
query.filter(User.name != 'ed')
like:(ilike)不区分大小写
query.filter(User.name.like('%ed%'))
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%'))))
not in:
query.filter(~User.name.in_(['ed','wendy','jack']))
is null:
query.filter(User.name==None)
# 或者是
query.filter(User.name.is_(None))
is not null:
query.filter(User.name != None)
# 或者是
query.filter(User.name.isnot(None))
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')
or:
from sqlalchemy import or_ query.filter(or_(User.name=='ed',User.name=='wendy'))
如果想要查看orm底层转换的sql语句,可以在方法后面不要再执行任何方法直接打印就可以看到了
1.4.1.8外键
从表中外键的字段,必须和父表的主键字段类型保持一致
外键约束有以下几项():
:父表数据被删除,会阻止删除。默认就是这一项。NO :在MySQL中,同。:级联删除。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.usernameclass 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")
另外,可以通过来指定反向访问的属性名称。是有多个。他们之间的关系是一个一对多的关系。
1.4.1.10ORM关系以及一对一
如果想要将两个模型映射成一对一的关系,那么应该在父模型中,指定引用的时候,要传递一个=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.usernameclass 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")
当然,也可以借助.orm.来简化代码:
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.usernameclass 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关系以及多对多
多对多的关系需要通过一张中间表来绑定他们之间的关系。在两个需要做多对多的模型中随便选择一个模型,定义一个属性,来绑定三者之间的关系,在使用的时候,需要传入一个=中间表。
先把两个需要做多对多的模型定义出来
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.titleclass 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
使用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。如果想要避免这种行为,应该将从表中的外键的=False。
在,只要将一个数据添加到中,和他相关联的数据都可以一起存入到数据库中了。这些是怎么设置的呢?其实是通过的时候,有一个关键字参数可以设置这些属性:
save-:默认选项。在添加一条数据的时候,会把其他和他相关联的数据都添加到数据库中。这种行为就是save-属性影响的。:表示当删除某一个模型中的数据的时候,是否也删掉使用和他关联的数据。-:表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉。当然如果父表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,不能用在多对多以及多对一上。并且还需要在子模型中的中,增加一个=True的参数。merge:默认选项。当在使用.merge,合并一个对象的时候,会将使用了相关联的对象也进行merge操作。:移除操作的时候,会将相关联的对象也进行移除。这个操作只是从中移除,并不会真正的从数据库中删除。all:是对save-, merge, -, , 几种的缩写。 1.4.1.13排序:
:可以指定根据这个表中的某个字段进行排序,如果在前面加了一个-,代表的是降序排序。
在模型定义的时候指定默认排序:有些时候,不想每次在查询的时候都指定排序的方式,可以在定义模型的时候就指定排序的方式。有以下两种方式:
= {
“”: title
即可让文章使用标题来进行排序。
正序排序与倒序排序:默认是使用正序排序。如果需要使用倒序排序,那么可以使用这个字段的desc()方法,或者是在排序的时候使用这个字段的字符串名字,然后在前面加一个负号。
1.4.1.、和切片操作: limit:可以限制每次查询的时候只查询几条数据。:可以限制查找数据的时候过滤掉前面多少条。切片:可以对Query对象使用切片操作,来获取想要的数据。可以使用slice(start,stop)方法来做切片操作。也可以使用[start:stop]的方式来进行切片操作。一般在实际开发中,中括号的形式是用得比较多的。希望大家一定要掌握。示例代码如下:
articles = session.query(Article).order_by(Article.id.desc())[0:10]
1.4.1.15懒加载
在一对多,或者多对多的时候,如果想要获取多的这一部分的数据的时候,往往能通过一个属性就可以全部获取了。比如有一个作者,想要或者这个作者的所有文章,那么可以通过user.就可以获取所有的。但有时候我们不想获取所有的数据,比如只想获取这个作者今天发表的文章,那么这时候我们可以给传递一个lazy=‘’,以后通过user.获取到的就不是一个列表,而是一个对象了。这样就可以对这个对象再进行一层过滤和排序等操作。
通过lazy='',获取出来的多的那一部分的数据,就是一个对象了。这种对象既可以添加新数据,也可以跟Query一样,可以再进行一层过滤。
总而言之一句话:如果你在获取数据的时候,想要对多的那一边的数据再进行一层过滤,那么这时候就可以考虑使用lazy=''。
lazy可用的选项:
:这个是默认选项。还是拿user.的例子来讲。如果你没有访问user.这个属性,那么就不会从数据库中查找文章。一旦你访问了这个属性,那么就会立马从数据库中查找所有的文章,并把查找出来的数据组装成一个列表返回。这也是懒加载。:这个就是我们刚刚讲的。就是在访问user.的时候返回回来的不是一个列表,而是对象。
:
根据某个字段进行分组。比如想要根据性别进行分组,来统计每个分组分别有多少人,那么可以使用以下代码来完成:
session.query(User.gender,func.count(User.id)).group_by(User.gender).all()
1.4.1.:
是对查找结果进一步过滤。比如只想要看未成年人的数量,那么可以首先对年龄进行分组统计人数,然后再对分组进行过滤。示例代码如下:
result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age >= 18).all()
1.4.1.17 join: join分为left join(左外连接)和right join(右外连接)以及内连接(等值连接)。参考的网页:在中,使用join来完成内连接。在写join的时候,如果不写join的条件,那么默认将使用外键来作为条件连接。query查找出来什么值,不会取决于join后面的东西,而是取决于query方法中传了什么参数。就跟原生sql中的 后面那一个一样。
比如现在要实现一个功能,要查找所有用户,按照发表文章的数量来进行排序。示例代码如下:
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.
子查询可以让多个查询变成一个查询,只要查找一次数据库,性能相对来讲更加高效一点。不用写多个sql语句就可以实现一些复杂的查询。那么在中,要实现一个子查询,应该使用以下几个步骤:
将子查询按照传统的方式写好查询代码,然后在query对象后面执行方法,将这个查询变成一个子查询。在子查询中,将以后需要用到的字段通过label方法,取个别名。在父查询中,如果想要使用子查询的字段,那么可以通过子查询的返回值上的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. 1.4.2.-
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.利用会话将数据添加到数据表中。
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.
用来做orm模型与数据库的迁移和映射
pip install alembic
1.下使用
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
修改.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:创建一个仓库。
:创建一个新的版本文件。
–:自动将当前模型的修改,生成迁移脚本。
-m:本次迁移做了哪些修改,用户可以指定这个参数,方便回顾。
:将指定版本的迁移文件映射到数据库中,会执行版本文件中的函数。如果有多个迁移脚本没有被映射到数据库中,那么会执行多个迁移脚本。
[head]:代表最新的迁移脚本的版本号。
:会执行指定版本的迁移文件中的函数。
heads:展示head指向的脚本文件版本号。
:列出所有的迁移版本及其信息。
:展示当前数据库中的版本号。
错误描述原因解决办法
: is not up to date.
主要是heads和不相同。落后于heads的版本。
将移动到head上。 head
: Can’t by ‘’
数据库中存的版本号不在迁移脚本文件中
删除数据库的表中的数据,重新执行 head
2.flask-下使用
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
修改.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.-
Flask-的作用是可以通过命令行的形式来操作Flask。例如通过命令跑一个开发版本的服务器、设置数据库,定时任务等。要使用Flask-,可以通过pip flask-安装最新版本。(这里降低了flask的版本pip3 flask==1.1.2)
@manager.command
def hello():#1.自定义commandprint("你好")
@manager.option("-u","--username",dest="username")
@manager.option("-a","--age",dest="age")
def u_a(username,age):#2.定义传参commandprint("当前用户是:%s,年龄是:%s"%(username,age))
from db_script import db_manager
manager.add_command("db",db_manager)
1.4.2.-
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.-WTF
Flask-WTF是简化了操作的一个第三方库。表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:CSRF保护,文件上传等。安装Flask-WTF默认也会安装.
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)
常用的验证器:
数据发送过来,经过表单验证,因此需要验证器来进行验证,以下对一些常用的内置验证器进行讲解:
验证器功能
验证上传的数据是否为邮箱。
验证上传的数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等。
原始数据的需要验证。如果不是特殊情况,应该使用。
长度限制,有min和max两个值进行限制。
数字的区间,有min和max两个值限制,如果处在这两个数字之间则满足。
自定义正则表达式。
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.和 1.5.2.
1.设置,在对象上使用set-方法。
@app.route("/")
def coo():resp=Response("hello")resp.set_cookie("username","zoe")#键,值return resp
2.删除,在对象上使用方法。
@app.route('/dele/')
def dele():resp=Response("done")resp.delete_cookie('username')#键return resp
3.设置的有效期
默认是浏览会话结束,即浏览器关闭后失效。
resp.set_cookie("username","zoe",max_age=60)#max_age(有效期多少秒)
from datetime import datetime,timedeltadue=datetime.now()+timedelta(days=30,hours=16)#使用格林尼治时间,即比北京时间少8小时resp.set_cookie("username","zoe",expires=due)#使用expires设置截止时间
4.设置的有效域名
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的域名和端口映射
1.5.2.
1.设置
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.获取
@app.route('/get_session/')
def get_session():username=session.get('username')return username or '没有拿到session'
3.删除
@app.route('/del_session/')
def del_session():# session.pop['username']session.clear()#删除session中的所有信息return '删除成功'
4…设置有效期,默认是When the 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 , 跨站域请求伪造)是一种网络的攻击方式,如果你访问了一个别有用心或病毒网站,这个网站可以在网页源代码中插入js代码,使用js代码给其他服务器发送请求(比如ICBC的转账请求)。那么因为在发送请求的时候,浏览器会自动的把发送给对应的服务器,这时候相应的服务器(比如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.上下文
1.单线程
from threading import Thread
request='123'
class Mythread(Thread):def run(self):global requestrequest='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钩子函数
:处理第一次请求之前执行。例如以下代码:
@app.before_first_requestdef first_request():print 'first time request'
:在每次请求之前执行。通常可以用这个装饰器来给视图函数增加一些变量。例如以下代码:
@app.before_requestdef before_request():if not hasattr(g,'user'):setattr(g,'user','xxxx')
:不管是否有异常,注册的函数都会在每次请求之后执行。
@app.teardown_appcontextdef teardown(exc=None):if exc is None:db.session.commit()else:db.session.rollback()db.session.remove()
:在使用模板的时候自定义过滤器。比如可以增加一个upper的过滤器(当然已经存在这个过滤器,本示例只是为了演示作用):
@app.template_filterdef upper_filter(s):return s.upper()
:上下文处理器。返回的字典中的键可以在模板上下文中使用。必须返回一个字典,无参数就返回{}空字典,例如:
@app.context_processorreturn {'current_user':'xxx'}
:接收状态码,可以自定义返回这种状态码的响应的处理方法。例如:
@app.errorhandler(404)def page_not_found(error):return 'This page does not exist',404
1.5.信号
flask中的信号使用的是一个第三方插件,叫做。
1.自定义信号
2.内置信号
flask.template_rendered:模版渲染完毕后发送,示例如下:from flask import template_rendereddef log_template_renders(sender,template,context,*args):print 'sender:',senderprint 'template:',templateprint 'context:',contexttemplate_rendered.connect(log_template_renders,app)
flask.:请求开始之前,在到达视图函数之前发送,订阅者可以调用之类的标准全局代理访问请求。示例如下:
def log_request_started(sender,**extra):print 'sender:',senderprint 'extra:',extra
request_started.connect(log_request_started,app)
flask.:请求结束时,在响应发送给客户端之前发送,可以传递,示例代码如下:
def log_request_finished(sender,response,*args):print 'response:',response
request_finished.connect(log_request_finished,app)
flask.n:在请求过程中抛出异常时发送,异常本身会通过传递到订阅的函数。示例代码如下:
def log_exception_finished(sender,exception,*args):print 'sender:',senderprint type(exception)
got_request_exception.connect(log_exception_finished,app)
flask.:请求被销毁的时候发送,即使在请求过程中发生异常,也会发送,示例代码如下:
def log_request_tearing_down(sender,**kwargs):print 'coming...'
request_tearing_down.connect(log_request_tearing_down,app)
flask.own:在应用上下文销毁的时候发送,它总是会被调用,即使发生异常。示例代码如下:
def log_appcontext_tearing_down(sender,**kwargs):print 'coming...'
appcontext_tearing_down.connect(log_appcontext_tearing_down,app)
1.5.-插件
pip install flask-restful
1.定义的视图
如果使用Flask-,那么定义视图函数的时候,就要继承自.类,然后再根据当前请求的来定义相应的方法。比如期望客户端是使用get方法发送过来的请求,那么就定义一个get方法。类似于。
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)
用工具来测试:
2.参数解析:Flask-插件提供了类似来验证提交的数据是否合法的包,叫做。
1.5.
1.安装启动:
:管理员身份
安装:.exe -d 。
启动:.exe -d start。
可能出现的问题: 提示你没有权限:在打开cmd的时候,右键使用管理员身份运行。
提示缺少.dll文件:将.dll文件拷贝到/.
不要放在含有中文的路径下面。
-d:这个参数是让在后台运行。
-m:指定占用多少内存。以M为单位,默认为64M。
-p:指定占用的端口。默认端口是11211。
-l:别的机器可以通过哪个ip地址连接到我这台服务器。如果是通过 start的方式,那么只能通过本机连接。如果想要让别的机器连接,就必须设置-l 0.0.0.0。
如果想要使用以上参数来指定一些配置信息,那么不能使用 start,而应该使用/usr/bin/的方式来运行。比如/usr/bin/ -u -m 1024 -p 11222 start。
3.操作:
可能出现的问题:‘’ 不是内部或外部命令,也不是可运行的程序或批处理文件。将功能打开。
set和add的区别:add是只负责添加数据,不会去修改数据。如果添加的数据的key已经存在了,则添加失败,如果添加的key不存在,则添加成功。而set不同,如果中不存在相同的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.操作:
安装
pip install python-memcached
连接
import memcachemc = 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':'xxx@qq.com','telphone':'111111'},time=60*5)#set多条键值对mc.get('telphone')mc.delete('email')mc.incr('age',delta=10)#默认是1mc.decr('read_count')
5.的安全性
的操作不需要任何用户名和密码,只需要知道服务器的ip地址和端口号即可。因此使用的时候尤其要注意他的安全性。这里提供两种安全的解决方案。
A.使用-l参数设置为只有本地可以连接:这种方式,就只能通过本机才能连接,别的机器都不能访问,可以达到最好的安全性。
B.使用防火墙,关闭11211端口,外面也不能访问。
ufw # 开启防火墙
ufw # 关闭防火墙
ufw deny # 防火墙以禁止的方式打开,默认是关闭那些没有开启的端口
ufw deny 端口号 # 关闭某个端口
ufw allow 端口号 #开启某个端口
1.5. 3.错误
1.: ‘’ for email .
解决办法:降低的版本
pip install wtforms==2.2.1
参考:
2.No named ‘flask.’
解决办法:改为
from flask_script._compat import text_type
3. name ‘’ from ‘’
解决办法:
pip3 install flask==1.1.2
参考: