flask=werkzeug+sqlalchemy_jinja
在系统级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目录下的
win
pip install virtualenvwrapper-win
mkvirtualenv abc#放在当前win用户的文件夹里面envs的文件夹下
workon abc#激活此环境
deactivate#推出此环境
rmvirtualenv abc#删除此环境
lsvirtualenv abc#列出环境
cdvirtualenv de#立即进入de环境
linux
pip install virtualenvwrapper
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.在渲染模版的时候,默认会从项目根目录下的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.添加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.使用一些配置信息,然后将他们组合成满足条件的字符串
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())
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.构建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)
- Integer:整形,映射到数据库中是int类型。
- Float:浮点类型,映射到数据库中是float类型。他占据的32位。
- Double:双精度浮点类型,映射到数据库中是double类型,占据64位。
- String:可变字符类型,映射到数据库中是varchar类型.
- Boolean:布尔类型,映射到数据库中的是tinyint类型。
- DECIMAL:定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。
- Enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。
- Date:存储时间,只能存储年月日。映射到数据库中是date类型。在Python代码中,可以使用
datetime.date
来指定。- DateTime:存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是datetime类型。
- Time:存储时间,可以存储时分秒。映射到数据库中也是time类型。在Python代码中,可以使用
datetime.time
- Text:存储长字符串。一般可以存储6W多个字符。如果超出了这个范围,可以使用LONGTEXT类型。映射到数据库中就是text类型。
- LONGTEXT:长文本类型,映射到数据库中是longtext类型。
- primary_key:设置某个字段为主键。
- autoincrement:设置这个字段为自动增长的。
- default:设置某个字段的默认值。在发表时间这些字段上面经常用。
- nullable:指定某个字段是否为空。默认值是True,就是可以为空。
- unique:指定某个字段的值是否唯一。默认是False。
- onupdate:在数据更新的时候会调用这个参数指定的值或者函数。在第一次插入这条数据的时候,不会用onupdate的值,只会使用default的值。常用的就是
update_time
(每次更新数据的时候都要更新的值)。- name:指定ORM模型中某个属性映射到表中的字段名。如果不指定,那么会使用这个属性的名字来作为字段名。如果指定了,就会使用指定的这个值作为参数。这个参数也可以当作位置参数,在第1个参数来指定。
func
上,其实没有任何聚合函数。但是因为他底层做了一些魔术,只要mysql中有的聚合函数,都可以通过func调用。 article = session.query(Article).filter(Article.title == "title0").first()
not equals:
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语句,可以在filter方法后面不要再执行任何方法直接打印就可以看到了
从表中外键的字段,必须和父表的主键字段类型保持一致
外键约束有以下几项(ondelete):
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是有多个。他们之间的关系是一个一对多的关系。
如果想要将两个模型映射成一对一的关系,那么应该在父模型中,指定引用的时候,要传递一个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))
多对多的关系需要通过一张中间表来绑定他们之间的关系。在两个需要做多对多的模型中随便选择一个模型,定义一个relationship属性,来绑定三者之间的关系,在使用relationship的时候,需要传入一个secondary=中间表。
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
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)
)
ORM层面删除数据,会无视mysql级别的外键约束。直接会将对应的数据删除,然后将从表中的那个外键设置为NULL。如果想要避免这种行为,应该将从表中的外键的nullable=False
。
在SQLAlchemy,只要将一个数据添加到session中,和他相关联的数据都可以一起存入到数据库中了。这些是怎么设置的呢?其实是通过relationship的时候,有一个关键字参数cascade可以设置这些属性:
order_by:可以指定根据这个表中的某个字段进行排序,如果在前面加了一个-,代表的是降序排序。
在模型定义的时候指定默认排序:有些时候,不想每次在查询的时候都指定排序的方式,可以在定义模型的时候就指定排序的方式。有以下两种方式:
mapper_args = {
“order_by”: title
}
即可让文章使用标题来进行排序。
正序排序与倒序排序:默认是使用正序排序。如果需要使用倒序排序,那么可以使用这个字段的desc()
方法,或者是在排序的时候使用这个字段的字符串名字,然后在前面加一个负号。
slice(start,stop)
方法来做切片操作。也可以使用[start:stop]
的方式来进行切片操作。一般在实际开发中,中括号的形式是用得比较多的。希望大家一定要掌握。示例代码如下:articles = session.query(Article).order_by(Article.id.desc())[0:10]
在一对多,或者多对多的时候,如果想要获取多的这一部分的数据的时候,往往能通过一个属性就可以全部获取了。比如有一个作者,想要或者这个作者的所有文章,那么可以通过user.articles就可以获取所有的。但有时候我们不想获取所有的数据,比如只想获取这个作者今天发表的文章,那么这时候我们可以给relationship传递一个lazy=‘dynamic’,以后通过user.articles获取到的就不是一个列表,而是一个AppenderQuery对象了。这样就可以对这个对象再进行一层过滤和排序等操作。
通过lazy='dynamic'
,获取出来的多的那一部分的数据,就是一个AppenderQuery
对象了。这种对象既可以添加新数据,也可以跟Query
一样,可以再进行一层过滤。
总而言之一句话:如果你在获取数据的时候,想要对多的那一边的数据再进行一层过滤,那么这时候就可以考虑使用lazy='dynamic'
。
lazy可用的选项:
select
:这个是默认选项。还是拿user.articles
的例子来讲。如果你没有访问user.articles
这个属性,那么sqlalchemy就不会从数据库中查找文章。一旦你访问了这个属性,那么sqlalchemy就会立马从数据库中查找所有的文章,并把查找出来的数据组装成一个列表返回。这也是懒加载。dynamic
:这个就是我们刚刚讲的。就是在访问user.articles
的时候返回回来的不是一个列表,而是AppenderQuery
对象。session.query(User.gender,func.count(User.id)).group_by(User.gender).all()
having是对查找结果进一步过滤。比如只想要看未成年人的数量,那么可以首先对年龄进行分组统计人数,然后再对分组进行having过滤。示例代码如下:
result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age >= 18).all()
result = session.query(User,func.count(Article.id)).join(Article).group_by(User.id).order_by(func.count(Article.id).desc()).all()
子查询可以让多个查询变成一个查询,只要查找一次数据库,性能相对来讲更加高效一点。不用写多个sql语句就可以实现一些复杂的查询。那么在sqlalchemy中,要实现一个子查询,应该使用以下几个步骤:
query
对象后面执行subquery
方法,将这个查询变成一个子查询。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()
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()
用来做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
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)
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()
Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:CSRF保护,文件上传等。安装Flask-WTF默认也会安装WTForms.
pip install flask-wtf
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)
常用的验证器:
数据发送过来,经过表单验证,因此需要验证器来进行验证,以下对一些常用的内置验证器进行讲解:
验证器 | 功能 |
---|---|
验证上传的数据是否为邮箱。 | |
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("验证码错误")
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.上传文件
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.设置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'
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.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.单线程
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全局使用,多线程。
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
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)
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工具来测试:
2.参数解析:Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,叫做reqparse。
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功能打开。
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.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