Flask笔记
技术栈: Flask-Blueprint蓝图+ORM框架-SQLAlchemy + MVC拆分 + RESTful接口 + 序列化。
Flask: 轻量级的Pyhton Web开发框架, 只提供WEB接口开发引擎(即相对于Django来说, 没有ORM、没有缓存、没有模块化等等)。
Flask使用场景: 可以快速开发某一个特定的业务, 且可以独立运行(微服务)。
from flask import Flask
app = Flask(__name__)
# app.config.from_object(config)
"""
Flask类是项目的核心,以后很多操作都是基于Flask这个类的对象
注册url、蓝图等有是基于这个类的对象
如果创建一个Flask对象,通过__name__参数传递
__name__参数的作用:
1.可以规定模板和静态文件的查找路径
2.以后一些Flask插件,eg:Flask-SQLAlchemy报错,那么Flask
可以通过这个参数找到具体的路径
@app.route()是一个装饰器,将url映射到下面的函数中去,访问网页的/目录时
会执行下面的函数,然后将这个函数返回。
"""
@app.route('/')
def hello_world():
a = 1
b = 0
c = a/b
return 'Hello World!'
if __name__ == '__main__':
app.run()
配置文件的两种方式
- 导入config.py文件
将配置信息写在config.py中在app.py中导入
使用app.config.from_object(config)
加载配置文件
- 使用
app.config.from_pyfile('config.txt',silent=True)
直接加载配置文件,文件类型可以是.py或.txt,设置slient
属性,可以在配置文件加载不成功时不抛出异常
URL与函数的映射
@app.route('/article/<int:article_id>/')
# <int:article_id>不设置类型时,默认设置字符串string类型
# <path:article_id> path可以接收多个路径
# <uuid:xx> 只接收uuid字符串
# <any:xx> 可以指定多种路径 (多个路径映射到同一个函数)
def article_detial(article_id):
return '您请求的文章是:%s' % article_id
@app.route('/article/<path:test>/')
def article_test(test):
return '您请求的路径是:%s' % test
# http://127.0.0.1:5000/article/asa/aaa/
# 您请求的路径是:asa/aaa
# /blog/<id>/
# /user/<id>/
@app.route('/<any(user,blog):url_path>/<id>/')
def any_text(url_path, id):
if url_path == 'blog':
return '您请求的路径是:%s' % (url_path + '/' + id)
else:
return '您请求的路径是:%s' % (url_path + '/' + id)
import uuid
print(uuid.uuid4())
@app.route('/u/<uuid:user_id>/')
def user_detail(user_id):
return '用户个人中心页面:%s' % user_id
接收用户传递的参数
- 使用path的形式(将参数传入路径中) 。希望页面可以被SEO优化,即可被搜素引擎搜到
- 使用字符串的方式,通过
?key=value
的形式传递的。 <内部使用>
# from flask import Flask, request
@app.route('/d/')
def d():
wd = request.args.get('wd')
ie = request.args.get('ie')
print(ie)
return '您通过字符串的方式传递的参数是:%s' % wd
# http://127.0.0.1:5000/d/?wd=python
# 您通过字符串的方式传递的参数是:python
# http://127.0.0.1:5000/d/?wd=python&ie=utf-8
url_for使用
@app.route('/')
def hello_world():
# print(url_for('my_list', page=1, count=2))
# /lis/1?count=2 以查询字符串的方式拼接在url后面
# /lis/
# /lis/1/
# return 'Hello Sami'
return url_for('my_list', page=1, count=2)
# from flask import Flask, request, url_for
# 位置参数 ,后面的都是关键字参数
# def url_for(endpoint, **values):
@app.route('/lis/<page>')
def my_list():
print(url_for('my_list'))
return 'my_list'
作用:
项目比较大,有多个视图函数时,使用url_for()
个人理解:反转url后,在hellow_world()函数中使用url_for()后,不用在浏览器手动输入url
from werkzeug.routing import BaseConverter
自定义URL转换器
自定义的url转换器需要满足一下几个条件:
1.转换器是一个类,必须继承自 from werkzeug.routing import BaseConverter
2.在转化器类中,实现to_python(self,value)方法,将会传递到view函数中作为参数
3.在转换器类中,实现to_url(self,value)方法,将会在调用url_for函数的时候生成符合要求的url形式
to_python
:实现to_python(self,value)方法,将会传递到view函数中作为参数
to_url
:将会在调用url_for函数的时候生成符合要求的url形式
# coding=gbk
from flask import Flask, url_for
from werkzeug.routing import BaseConverter
app = Flask(__name__)
"""
1.首先导入 from werkzeug.routing import BaseConverter,
添加新的url转换类别
2.实现一个类,继承BaseConverter
3.在自定义的类中,重写 regex<符合要求的正则规则>
4.将自定义类,映射到'app.url_map.converters['tel'] = TelephonCoverter'
5.函数内,可使用该自定义的url转换器。
"""
class TelephoneCoverter(BaseConverter):
regex = r'1[85743]\d{9}'
url的类型均继自BaseCoventer,重写其中的to_python方法,可以将传入的url通过to_python里面定义的函数对视图函数传入的url进行处理,返回需要的值,
例如: 传入a+b,将以列表的形式输出
to_url:使用到了url_for() 将其中的参数,以某种方式返回。
class ListConverter(BaseConverter):
def to_python(self, value):
# value即传进去的参数的值
print(value)
return value.split('+')
def to_url(self, value):
# print(value) #['a', 'b']
print('*'*30)
print(value) #
return '+'.join(value)
# return 'hello'
app.url_map.converters['tel'] = TelephoneCoverter
app.url_map.converters['list'] = ListConverter
@app.route('/telephone/1/<tel:my_tel>/')
def tel(my_tel):
return '您输入的手机号码是%s' % my_tel
# eg: 通用做法
# 用户在访问/post/a+b/ 请求ab两个板块的所有帖子。
@app.route('/post/<list:board>/')
def posts(board):
# board = board.split('+')
print(board) # 您查找的板块是 ['a', 'b']
return '您查找的板块是 %s' % board
@app.route('/')
def hello_world():
print(url_for('posts', board=['a', 'b'])) # /post/hello/
return 'hello_world'
if __name__ == '__main__':
app.run()
在局域网内访问自己
指定host为0.0.0.0和端口号
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000)
重定向
永久重定向:状态码301 (有利于搜索引擎优化)
暂时重定向:状态码302
在Flask中,重定向是通过flask.redirect(location,code=302)函数实现,location表示需要重定向到的url,应该配合url_for()函数来使用,code表示采用那个重定向(默认为302)
# coding=gbk
from flask import Flask, url_for, redirect, request
app = Flask(__name__)
@app.route('/login/')
def login():
return 'Login'
@app.route('/profile/')
def profile():
if request.args.get('name'):
return '个人中心页面'
else:
return redirect(url_for('login'))
@app.route('/')
def hello():
return 'hello world'
if __name__ == '__main__':
app.run()
关于响应response
什么样的数据类型可以被视图函数返回,被页面接收?
视图函数的返回值会被自动转换成一个响应对象,Flask的转换逻辑如下:
- 如果返回的是一个合法的响应对象,则直接返回
- 如果返回的是一个字符串,Flask会重建一个werkzeug.wrappers.Response对象,Response将该字符串作为主体,状态码为200,MIME类型为text/html,然后返回该Response对象。
- 如果返回的是一个元组,元组中的数据类型是(response.status.headers)。status值默认设置为200状态码,headers可以是一个列表或者字典,作为额外的消息头
- 如果以上条件都不满足,Flask会假设返回值是一个合法的WSGI应用程序,并通过Response.force_type(rv,request.environ)转化为一个请求对象。
# coding=gbk
from flask import Flask, Response,jsonify
app = Flask(__name__)
"""
将视图函数中返回的字符串,转换成json对象,然后返回
RESTful-API 返回的数据均为json数据
"""
class JSONResponse(Response):
"""
这个方法只有在视图函数返回非字符串时、非元组、非Response对象时才会调用
Response:视图函数的返回值
自定义响应:
- 必须继承Response类
- 必须实现方法 force_type(cls,response,,environ=None)
- 必须指定 app.response_class 为自定义的Response
"""
@classmethod
def force_type(cls, response, environ=None):
print(response)
print(type(response))
return Response('hello world')
app.response_class = JSONResponse
@app.route('/list3/')
def list3():
return {'username':'sami', 'age':22}
# 字符串
@app.route('/')
def hello():
# Response('hello world',status=200,mimetype='text/html')
return 'hello world'
@app.route('/list1/')
def list1():
return Response('list12')
# 元组
@app.route('/list2/')
def list2():
return 'list2', 200, {'X-HEADER': 'hahah'}
if __name__ == '__main__':
app.run()
jinja2模块和查找路径
# coding=gbk
from flask import Flask,render_template
app = Flask(__name__,template_folder='C:/templates')
# 默认的模板路径 template_folder="templates",
@app.route('/')
def hello():
return render_template('index.html')
# return render_template('posts/post1.html')
if __name__ == '__main__':
app.run()
模板传参及其技巧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我爱学习!</h1>
{# <p>{{ context.username }}</p>#}
{# <p>{{ username }}</p>#}
{# <p>{{ age }}</p>#}
{# <p>{{ telephone }}</p>#}
<p>{{ telephone }}</p>
<p>{{ children.name }}</p>
<p>{{ children['name1'] }}</p>
</body>
</html>
# coding=gbk
from flask import Flask, render_template
# app = Flask(__name__,template_folder='C:/templates')
app = Flask(__name__)
# 默认的模板路径 template_folder="templates",
@app.route('/')
def hello():
context = {
'username': 'sami',
'age': 18,
'city': '西安',
'telephone': 13991555555,
'children': {
'name1': 'jack',
'name': 'jiffy'
}
}
return render_template('index.html', **context)
# return render_template('index.html', context=context)
# return render_template('posts/post1.html')
if __name__ == '__main__':
app.run()
1.在使用render_template
渲染模板的时候,可以传递关键字参数,以后可以直接在模板中使用。
2.参数过多时,将参数放在字典中,然后再传字典参数的时候,使用**args,将字典打散成关键参数
模板中url_for()的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>index2</h1>
{# {{ 用来存放变量 }}#}
{# {% 用来执行函数后者逻辑代码 %}#}
<a href="{{ url_for('login') }}">login</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>login</h1>
</body>
</html>
# coding=gbk
from flask import Flask, render_template, url_for
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index1.html')
@app.route('/asc/login/')
def login():
url_for('login')
return render_template('login.html')
# return 'login'
if __name__ == '__main__':
app.run()
过滤器的基本使用
在页面中:例如计算发布的文字时多久前发布的
length是jinja2内置的过滤器
过滤器是通过管道符号(|)进行使用的,例如:{{ name|length }},将返回name的长度。过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。Jinja2中内置了许多过滤器,在这里可以看到所有的过滤器,现对一些常用的过滤器进行讲解:
abs(value):返回一个数值的绝对值。 例如:-1|abs。
default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。name|default(‘xiaotuo’)——如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。
escape(value)或e:转义字符,会将<、>等符号转义成HTML中的符号。例如:content|escape或content|e。
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):计算一个长字符串中单词的个数。
Flask视图
add_url_rule和app.route
# coding=gbk
from flask import Flask, url_for
app = Flask(__name__)
# url和函数映射的方式
# 方式1:装饰器和函数构成关联关系
@app.route('/', endpoint='index')
def index():
print(url_for('zhiliao'))
return 'hello'
# 方式2:通过函数关联
def my_list():
return 'my_list'
# 请求上下文
with app.test_request_context():
print(url_for('index')) # /
app.add_url_rule('/list/', endpoint='zhiliao', view_func=my_list)
"""
add_url_rule三个参数解释:
第一个参数:函数对应的url规则,满足条件和app.route()的第一个参数一样,必须以'/'开始
endpoint:站点,就是在使用url_for()进行反转的时候,这个里面传入的第一个参数就是这个
endpoint对应的值。这个值也可以不指定,那么默认就会使用函数的名字作为endpoint的值
view_func:对应的函数,即这个url对应的是哪一个函数,注意,这里函数只需要写函数名字,
不要加括号,加括号表示将函数的返回值传给了view_func参数了。程序就会直接报错。
"""
# endpoint的作用:没有指定时,通过my_list(函数名)找到视图函数,指定后,再url反转时,只能使用
# endpoint指定的名字获取
if __name__ == '__main__':
app.run()
add_url_rule(rule,endpoint=None,view_func=None)这个方法用来添加url于视图函数的映射。如果没有填写
endpoint,那么默认使用
view_func的名字作为
endpoint。以后在使用
url_for的时候,就要看在映射的时候有没有传递
endpoint参数,如果传递了,那么就应该使用
endpoint指定的字符串,如果没有传递,那么就应该使用
view_func`的名字。
app.route(rule,**options)装饰器:
这个装饰器底层,其实也是使用add_url_rule
来实现url与视图函数映射的。
类视图
我们知道,在flask中,我们写的视图都是以函数形式写的,所以一般简称函数视图。其实视图我们也可以使用类来实现,类视图的好处是支持继承,但是类视图不能跟函数视图一样,写完类视图之后,我们还需要通过app.add_url_rule( url_rule, view_func )
来进行注册。在flask中,有两种类视图
标准视图
标准类视图是继承自flask.views.View
,并且在子类中必须实现dispatch_request
方法(分发/处理请求),这个方法就是相当于视图函数,所有的逻辑操作我们都要放在这个里面完成。也必须的返回一个值,函数视图能返回什么类型的值,这里就可以返回什么类型的值。其实类视图和函数视图基本都是一样的,只是我们在做添加url
规则的时候不一样而已,必须使用app.add_url_rule()
与视图的映射,使用as_view()
将类方法转换成函数。
# coding=gbk
from flask import Flask, views,url_for
app = Flask(__name__)
# 类视图不能使用装饰器
class ListView(views.View):
def dispatch_request(self):
return "list view"
# view_func=ListView.as_view() 将类视图返回成一个函数
app.add_url_rule('/list/', endpoint='list', view_func=ListView.as_view('list'))
with app.test_request_context():
print(url_for('list')) # /list/
@app.route('/')
def index():
return 'hello index'
if __name__ == '__main__':
app.run()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>login</h1>
<p>{{ ads }}</p>
<p>{{ username }}</p>
<p>{{ age }}</p>
<p>{{ sex }}</p>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Register</title>
</head>
<body>
<h1>Register</h1>
<p>{{ ads }}</p>
</body>
</html>
# coding=gbk
from flask import Flask, views, url_for, jsonify, render_template
app = Flask(__name__)
# 有几个url需要返回json数据
class JSONView(views.View):
def get_data(self):
raise NotImplementedError
def dispatch_request(self):
return jsonify(self.get_data())
class ListView(JSONView):
def get_data(self):
return {'username': 'sami', 'age': 22, 'city': 'XiAn'}
# 类视图不能使用装饰器
# class ListView(views.View):
# def dispatch_request(self):
# return "list view"
# view_func=ListView.as_view() 将类视图返回成一个函数
app.add_url_rule('/list/', endpoint='list', view_func=ListView.as_view('list'))
# with app.test_request_context():
# print(url_for('list')) # /list/
# 有几个视图,是需要返回相同的变量
class ADSView(views.View):
def __init__(self):
super(ADSView, self).__init__()
self.context = {
'ads': '今年不回家了嘛?'
}
"""
1.写新的类继承自父类view.View重写里面的方法
2.在子类中,继承新写的类,重构dispatch_request()方法
"""
class LoginView(ADSView):
# class LoginView(views.View):
def dispatch_request(self):
self.context.update({
'username': 'sami',
'age': 18,
'sex': 'mela'
})
# return render_template('login.html', ads='今年不回家')
return render_template('login.html', **self.context)
class RegisterView(views.View):
def dispatch_request(self):
return render_template('register.html', ads='今年不回家')
app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))
@app.route('/')
def index():
return 'hello index'
if __name__ == '__main__':
app.run()
类视图的好处:
可以继承,将一些共性的东西抽取出来放到父视图中,子视图可以直接使用。
基于调度(请求)方法的视图
在flask中,还提供了另外一种类视图flask.views.MethodView
,对每个HTTP的请求方法执行不同的函数,映射到对应小写的同名方法上面。
# coding=gbk
from flask import Flask, views, render_template, request
app = Flask(__name__)
@app.route('/')
def index():
# 基于函数的就比较复杂 不够简洁
if request.method == 'GET':
return 'hello index'
else:
# 写一些post方法请求代码
pass
class LoginView(views.MethodView):
def __render(self,error=None):
return render_template('login.html',error=error)
# def get(self, error=None):
# return render_template('login.html', error=error)
def get(self):
return self.__render()
def post(self):
username = request.form.get('username')
password = request.form.get('password')
if username == 'zhiliao' and password == '123456':
return 'login successful'
else:
# return render_template('login.html', error='用户名或密码错误')
# 此处直接调用get方法
# return self.get(error='用户名或密码错误')
return self.__render(error='用户名或密码错误')
app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
if __name__ == '__main__':
app.run()
类视图中使用装饰器
# coding=gbk
from flask import Flask, request, views
from functools import wraps
app = Flask(__name__)
# 函数视图定义装饰器
def login_required(func):
@wraps(func) # 必须装饰,要不然会丢失自己的特性
def wrapper(*args, **kwargs):
# /login/?username=xxx
username = request.args.get('username')
if username and username == 'zhiliao':
return func(*args, **kwargs)
else:
return '请先登录'
return wrapper
@app.route('/settings/')
# 自己写的装饰器必须在route()下面,在函数的上面
@login_required
def settings():
return '这是设置界面'
@app.route('/')
def index():
return 'hello index'
# 给类添加装饰器
class ProfileView(views.View):
decorators = [login_required] # 列表或者元组类型,里面装了所有的装饰器
def dispatch_request(self):
return 'self-center'
app.add_url_rule('/profile/', view_func=ProfileView.as_view('profile'))
if __name__ == '__main__':
app.run()
蓝图的使用
蓝图的作用:
是Flask更模块化,解耦合,结构更加清晰,可以将相同模块的视图函数放在同一个模块下,在同一个文件中,方便管理。
# coding=gbk
from flask import Blueprint
user_bp = Blueprint('user', __name__, url_prefix='/user')
# 个人中心的url与视图函数
@user_bp.route('/profile/')
def profile():
return 'profile'
@user_bp.route('/settings/')
def settings():
return 'settings'
# coding=gbk
from flask import Flask
from blue_print.user import user_bp
from blue_print.news import news_bp
"""
用户模块
新闻模块
电影模块
读书模块
"""
app = Flask(__name__)
app.register_blueprint(user_bp)
app.register_blueprint(news_bp)
@app.route('/')
def index():
return 'hello index'
if __name__ == '__main__':
app.run()
1.在模块文件下导入蓝图from flask import Blueprint
2.创建蓝图对象user_bp = Blueprint('user', __name__)
3.写出相关的视图函数@user_bp.route('/profile/') def profile():
4.在启动app.py里先导入,在注册蓝图,即可用。
[url技巧]
同一模块下的url添加url前缀,需要在蓝图对象中添加prefix
参数,如
user_bp = Blueprint('user', __name__, url_prefix='/user')
蓝图中模板文件寻找规则
# coding=gbk
from flask import Blueprint, render_template
news_bp = Blueprint('news', __name__, url_prefix='/news', template_folder='')
# url_prefix='/news', 指定前缀
# template_folder='zhiliao'(某个包下的文件夹) 指定模板目录 相对文件路径取决于__name__
"""
模板文件寻找规则
模板寻找,首先在templates里面寻找,若没有,则在蓝图指定的目录下寻找
"""
@news_bp.route('/list/')
def news_list():
return render_template('news_list.html')
@news_bp.route('/detail/')
def news_details():
return 'This is news_details'
蓝图静态文件的寻找规则
- 在模板文件中,加载静态文件,如果使用url_for(‘static’),只会在app指定的静态文件目录下查找静态文件
- 如果在加载静态文件的手,指定蓝图的名字,
news.static
,此时会在蓝图指定的文件夹下查找。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>News Details</title>
<link rel="stylesheet" href="{{ url_for('news.static',filename='test.css') }}">
</head>
<body>
<h1>News Details</h1>
<img src="../static/NeZha.gif" alt="">
</body>
</html>
# coding=gbk
from flask import Blueprint, render_template
news_bp = Blueprint('news', __name__, url_prefix='/news',static_folder='css_test')
# url_prefix='/news', 指定前缀
# template_folder='zhiliao'(某个包下的文件夹) 指定模板目录 相对文件路径取决于__name__
"""
模板文件寻找规则
模板寻找,首先在templates里面寻找,若没有,则在蓝图指定的目录下寻找
"""
@news_bp.route('/list/')
def news_list():
return render_template('news_list.html')
@news_bp.route('/detail/')
def news_details():
return 'This is news_details'
蓝图下url_for()注意事项
使用蓝图后,想反转蓝图中的视图函数为url,应该在使用url_for的时候指定蓝图,如news.news_list
,否则将无法找到这个endpoint,在模板中同样需要满足这个条件。
即使在同一蓝图中反转视图函数,也需要指定蓝图的名字。
# coding=gbk
from flask import Flask,url_for,render_template
from blue_print.user import user_bp
from blue_print.news import news_bp
"""
用户模块
新闻模块
电影模块
读书模块
"""
app = Flask(__name__)
app.register_blueprint(user_bp)
app.register_blueprint(news_bp)
@app.route('/')
def index():
print(url_for('news.news_list')) # /news/list/
# return render_template('news_list.html')
return render_template('index.html')
if __name__ == '__main__':
app.run()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="{{ url_for('news.news_list') }}">新闻列表</a>
</body>
</html>
子域名实现
1.使用蓝图技术
2.在创建蓝图对象的时候,需要传递一个subdomain='cms'
参数,来指定子域名的前缀,如cms_bp = Blueprint('cms', __name__, subdomain='cms')
3.在启动文件中,需要配置`app.config[‘SERVER_NAME’] = 'jd.com:5000
- cms.localhost:5000 localhost不能有子域名
- cms.127.0.01:5000 ip地址不能有子域名
4.C:\Windows\System32\drivers\etc\hosts文件下,添加域名和本机的映射
- 主域名和子域名都需要映射
SQLAlchemy介绍和基本使用
数据库是一个网站的基础。Flask
可以使用很多种数据库。比如MySQL
,MongoDB
,SQLite
,PostgreSQL
等。这里我们以MySQL
为例进行讲解。而在Flask
中,如果想要操作数据库,我们可以使用ORM
来操作数据库,使用ORM
操作数据库将变得非常简单。
在讲解Flask
中的数据库操作之前,先确保你已经安装了以下软件:
-
mysql
:如果是在windows
上,到官网下载。如果是ubuntu
,通过命令sudo apt-get install mysql-server libmysqlclient-dev -yq
进行下载安装。 -
MySQLdb
:MySQLdb
是用Python
来操作mysql
的包,因此通过pip
来安装,命令如下:pip install mysql-python
。python2使用 -
pymysql
:pymysql
是用Python
来操作mysql
的包,因此通过pip
来安装,命令如下:pip3 install pymysql
。如果您用的是Python 3
,请安装pymysql
。 -
SQLAlchemy
:SQLAlchemy
是一个数据库的ORM
框架,我们在后面会用到。安装命令为:pip3 install SQLAlchemy
。# coding=gbk from sqlalchemy import create_engine HOSTNAME = '111111' PORT = '3306' DATABASE = '111111' USERNAME = 'root' PASSWORD = '1111111' # 用户名:密码@数据库地址:端口号/数据库名字 # DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE) DB_URL = 'mysql+pymysql://{username}:{password}@' \ '{host}:{port}/{db}?charset=utf8'.format(username=USERNAME, password=PASSWORD, port=PORT, db=DATABASE,host=HOSTNAME) engine = create_engine(DB_URL) # 创建引擎 # 判断是否连接成功 conn = engine.connect() # 创建连接数据库的对象 获取连接指针 result = conn.execute('select 1') print(result.fetchone())
首先从sqlalchemy
中导入create_engine
,用这个函数来创建引擎,然后用engine.connect()
来连接数据库。其中一个比较重要的一点是,通过create_engine
函数的时候,需要传递一个满足某种格式的字符串,对这个字符串的格式来进行解释:
dialect+driver://username:password@host:port/database?charset=utf8
1
dialect
是数据库的实现,比如MySQL
、PostgreSQL
、SQLite
,并且转换成小写。driver
是Python
对应的驱动,如果不指定,会选择默认的驱动,比如MySQL的默认驱动是MySQLdb
。username
是连接数据库的用户名,password
是连接数据库的密码,host
是连接数据库的域名,port
是数据库监听的端口号,database
是连接哪个数据库的名字。
如果以上输出了1
,说明SQLAlchemy
能成功连接到数据库。
ORM
对象关系映射**(英语:Object Relational Mapping,简称ORM,或O/RM,或**O/R mapping)
对象模型与数据库表的映射。
Person类 – > 数据库中的一个表
Person类中的属性 --> 数据库中的字段
Person类对象 --> 数据库表中的一条数据
随着项目越来越大,采用写原生SQL的方式在代码中会出现大量的SQL语句,那么问题就出现了:
- SQL语句重复利用率不高,越复杂的SQL语句条件越多,代码越长。会出现很多相近的SQL语句。
- 很多SQL语句是在业务逻辑中拼出来的,如果有数据库需要更改,就要去修改这些逻辑,这会很容易漏掉对某些SQL语句的修改。
- 写SQL时容易忽略web安全问题,给未来造成隐患。
ORM,全称Object Relational Mapping,中文叫做对象关系映射,通过ORM我们可以通过类的方式去操作数据库,而不用再写原生的SQL语句。通过把表映射成类,把行作为实例,把字段作为属性,ORM在执行对象操作的时候最终还是会把对应的操作转换为数据库原生语句。使用ORM有许多优点:
- 易用性:使用ORM做数据库的开发可以有效的减少重复SQL语句的概率,写出来的模型也更加直观、清晰。
- 性能损耗小:ORM转换成底层数据库操作指令确实会有一些开销。但从实际的情况来看,这种性能损耗很少(不足5%),只要不是对性能有严苛的要求,综合考虑开发效率、代码的阅读性,带来的好处要远远大于性能损耗,而且项目越大作用越明显。
- 设计灵活:可以轻松的写出复杂的查询。
- 可移植性:SQLAlchemy封装了底层的数据库实现,支持多个关系数据库引擎,包括流行的MySQL、PostgreSQL和SQLite。可以非常轻松的切换数据库。
使用SQLAlchemy:
要使用ORM来操作数据库,首先需要创建一个类来与对应的表进行映射。现在以User表来做为例子,它有自增长的id、name、fullname、password这些字段,那么对应的类为:
1.创建一个ORM模型,这个ORM模型继承自sqlalchemy提供的基类
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base(engine)
2.在ORM模型中创建一些属性,来跟表中的字段一一映射,这些属性必须是
sqlalchemy给我们提供的数据类型
from sqlalchemy import create_engine, Column, Integer, String
class person(Base):
__tablename__ = 'person' # 1
# 2.属性和字段一一映射 表的结构:
id = Column(Integer, primary_key=True, autoincrement=True) # 创建的属性必须属性Column对象
name = Column(String(10))
age = Column(Integer)
3.将创建的好的ORM模型,映射到数据库中。
Base.metadata.create_all()
# coding=gbk
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
HOSTNAME = '00000'
PORT = '3306'
DATABASE = 'frist_sqlalchemy'
USERNAME = 'root'
PASSWORD = '00000'
# 用户名:密码@数据库地址:端口号/数据库名字
# DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
DB_URL = 'mysql+pymysql://{username}:{password}@' \
'{host}:{port}/{db}?charset=utf8'.format(username=USERNAME, password=PASSWORD, port=PORT, db=DATABASE,
host=HOSTNAME)
engine = create_engine(DB_URL) # 创建引擎
# 创建对象的基类: 可以创建ORM模型
Base = declarative_base(engine)
class person(Base):
__tablename__ = 'person' # 1
# 2.属性和字段一一映射 表的结构:
id = Column(Integer, primary_key=True, autoincrement=True) # 创建的属性必须属性Column对象
name = Column(String(10))
age = Column(Integer)
# 3.将创建好的ORM模型,映射到数据库中
Base.metadata.create_all()
# 使用后,改变模型字段,就不会被改变
SQLAlchemy对数据的增删改查操作
用session做数据的增删改查
1.构建sessison对象,所有和数据库的ORM操作都必通过一个叫session
的会话对象来实现。
获取会话对象如下:
# 首先要获取session对象 sessionmaker是一个类
# Session = sessionmaker(engine) # sessionmaker的对象
# session = Session() # 调用对象,才能拿到数据库的操作对象
session = sessionmaker(engine)() # 与上述代码同作用,更简洁
# coding=gbk
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
HOSTNAME = '111111'
PORT = '3306'
DATABASE = 'frist_sqlalchemy'
USERNAME = 'root'
PASSWORD = '111111111'
# 用户名:密码@数据库地址:端口号/数据库名字
# DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
DB_URL = 'mysql+pymysql://{username}:{password}@' \
'{host}:{port}/{db}?charset=utf8'.format(username=USERNAME, password=PASSWORD, port=PORT, db=DATABASE,
host=HOSTNAME)
engine = create_engine(DB_URL) # 创建引擎
# 调用declarative_base()传入引擎,可以创建对象的基类,通过基类便于创建orm模型
Base = declarative_base(engine)
"""
1.创建一个ORM模型,这个ORM模型继承自sqlalchemy提供的基类
2.在ORM模型中创建一些属性,来跟表中的字段一一映射,这些属性必须是
sqlalchemy给我们提供的数据类型
3.将创建的好的ORM模型,映射到数据库中,
"""
class Person(Base):
__tablename__ = 'person' # 1
# 2.属性和字段一一映射 表的结构:
id = Column(Integer, primary_key=True, autoincrement=True) # 创建的属性必须属性Column对象
name = Column(String(10))
age = Column(Integer)
country = Column(String(50))
def __str__(self):
return '<Person(name:%s,age:%s,country:%s)>' % (self.name, self.age, self.country)
Base.metadata.create_all()
# 首先要获取session对象 sessionmaker是一个类
# Session = sessionmaker(engine) # sessionmaker的对象
# session = Session() # 调用对象,才能拿到数据库的操作对象
session = sessionmaker(engine)()
# 增
def add_data():
p1 = Person(name='sami', age=18, country='china') # 初始化对象
p2 = Person(name='jack', age=18, country='japan')
# 添加数据需要先将数据添加到session中
session.add_all([p1, p2]) # session底层像是有一个列表,并未真的写入到数据库之中
session.commit() # 将数据提交到数据库
# 查
def search_data():
# all_person = session.query(Person).all()
# # print(all_person)
# for p in all_person: # 遍历打印出所有具体信息
# print(p)
# all_person = session.query(Person).filter_by(name='sami').all()
# for i in all_person:
# print(i)
# all_person = session.query(Person).filter(Person.name == 'sami').all()
# for i in all_person:
# print(i)
# 根据主键(id)查找
p3 = session.query(Person).get(2)
print(p3)
# 改
def update_data():
person = session.query(Person).first()
person.name = 'lucy'
session.commit()
# 查
def delete_data():
person = session.query(Person).first()
session.delete(person)
session.commit()
if __name__ == '__main__':
# add_data()
# search_data()
# update_data()
delete_data()
print('Already Done')
column常用的数据类型
1. Integer:整型,映射到数据库中是int类型。
2. Float:浮点类型,映射到数据库中是float类型。它占据的32位。
3. Double:双精度浮点类型,映射到数据库中是double类型,占据64位。
4. String:可变字符类型,映射到数据库中是varchar类型。
5. Boolean:布尔类型,映射到数据库中的是tinyint类型。
6. DECIMAL:定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。
7. Enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。在ORM模型中,使用Enum来作为枚举。
8. Date:存储时间,只能存储年月日。映射到数据库中是date类型。在Python代码中,可以使用datetime.date
来指定。
9. DateTime:存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是datetime类型。在Python代码中,可以使用datetime.datetime
来指定。
10. Time:存储时间,可以存储时分秒。映射到数据库中也是time类型。在Python代码中,可以使用datetime.time
来指定。
11. Text:存储长字符串。一般可以存储6W多个字符。如果超出了这个范围,可以使用LONGTEXT类型。映射到数据库中就是text类型。
12. LONGTEXT:长文本类型,映射到数据库中是longtext类型。