flask
运行流程
-
creat一个app对象,通过Flask(name)来注册方法,然后run_app
-
通过flask_script将app注册到manager 可以自定义一些其他的command manager=Manager(app)
-
在creat_app方法中注册app的一些功能模块
moment=Monment() moment.init_app(app)
-
app.register_blueprint() 注册蓝图:
auth_blueprint=Blueprint('sd', _ name _)
app.register_blueprint(auth_blueprint, url_prefix='/auth')
博客项目
将程序实例的创建推迟到配置文件的载入后
推迟到工厂函数中
app=Flask(name) app的名称
app.config 加载配置文件
使用蓝本来处理路由
. 从init.py 中引入
- 避免循环导入依赖的方法 依赖放在最后面
密码
通过散列来保存密码
通过设置属性来调用方法
- 通过使用itsdangerous生成确认令牌
包含用户id,感觉和散列有点像
必须先提交数据库,然后再邮件认证
用户
认证
注册先存入数据库,然后根据数据库返回的序列号生成一个链接,email发送,如果点击链接,跳转到函数,让数据库中is_confirmed那项为true
如果没认证,每次登陆都跳转到index页面
- 通过token 来得到data,data是一个字典类型
然后 data.get(id)来得到用户的序号
_external=True 参数在
url_for()中 的路由的参数是函数的名字
- 通过before login 来确定是否认证,如果没认证就跳转到认证页面
用户登录
让数据库 User models 增加 userMinxin from flask_login导入的。
- 从view页面得到当前user的数据库模型,user.get_or_create(id)
重置密码:
两种重置密码方式
一种是登录的情况下,一种是非登录的情况下,都应该邮件确认,保证安全
和邮件认证用户一个道理,但不同的是,这个token中要包含用户的信息,那个token是固定的密码.
所以这个token解密后应该是 包含用户信息的。
token的有效载荷是固定的,不能被伪造。 有效载荷中包含用户的信息以及秘钥的信息
{'reset_password':user_id,'exp':token_expiration}
python2.7用serilize包, 把需要添加的值dump进去
需不需要一个实用的工具包
来处理琐碎的事情
比如说生成密码token,发送邮件什么的。
反正发送邮件要交给后台来处理
pip
安装到某个文件夹下的txt中
pip install -r requirements/dev.txt
测试
单元测试
文件都写在tests文件夹下
在manage.py中写的命令
@manager.command
def test():
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
- python manage.py test
操作权限
关注用户:1
发表评论:2
写文章:4
管理评论:8
管理员权限:128
用户权限
用户的等级
匿名:0
正式用户:7
协管员:15
管理员:255
current_user方法
is_anonymous 判断是否是匿名用户
蓝本
来处理路由,不同的功能使用不同的蓝本
相当于django中的分开处理路由
在工厂函数中注册蓝本,相当于django在主页面的Url添加辅助功能的url
template
template中可以读取txt路径,作为html的内容
路径问题
模板的路径是相对于程序模板文件夹来说的
base中auth.logout对应的是方法名,而不是路径名
方法名和路径名好像必须要一样。。。
在html中添加动态路径
路由跳转
接受函数名作为参数
url_for('main.user',username=current_user.username)
return redirect(url_for('main.user',username=current_user.username))
flask-login
将login_manager添加在login中
LoginManager 对象的 session_protection 属性可以设为 None、'basic' 或 'strong',以提 供不同的安全等级防止用户会话遭篡改。设为 'strong' 时,Flask-Login 会记录客户端 IP 地址和浏览器的用户代理信息,如果发现异动就登出用户
表单: user email password remember submit
数据库
sqlalchemy
用来更新Model表的参数
类型于django migration 和migrate
-
初始化 创建migrations文件夹
python manage.py db init -
迁移 相当于django中的migrate
python manage.py db migrate -m 'second migrate' -
实际迁移到数据库中 相当于django makemigraions
python hello.py db upgrade
- event sqlalchemy set 时间的监听程序,只要进行了set 就会调用on_changed_body函数
on_change_body函数将markdown渲染为html,然后返回
联结操作
在文章页面只显示用户关注用户的文章
Post.join(Follow,Follow.follow_id==Post.author.id)
相关知识
-
主键是不唯一的
-
casacde 级联删除的意思,当删除外键依赖的主表时,子表内容自动删除
api
- order_by.all()
- filter_by().first()
- 对主键进行查找 .get_or_404()
分页
- query.order_by(Post.timestamp.desc()).paginaton()
分页的属性: - items 查询到的记录
- query 源查询
- page 当前页数
- prev_num 上一页的页数
- next_num 下一页的页数
- has_next 是否有上一页
-has_prev 是否有上一页 - pages 总页数
- per_page 每页的记录数量
- total 记录总数、
在对象上调用的方法
iter_pages 迭代器,返回一个页数列表
写好一个分页页面, _macros,然后在其他需要分页的html中将这个页面插入
alembic
- 初始化
mbic init YOUR_ALEMBIC_DIR
relationship
关系之间的便捷调用
Student model:
classes=db.relationship('class',secondary=restrations,backerf=db.backref('students',lazy='dynamic'),
lazy='dynamic')
student的clsses属性和class表建立relationship,
同时提供一个反向声明, class表中的对象可以直接classe.students来筛选出选了该课的学生
flask中session操作
- 增加 和更新
db.session.add(user)
db.session.commit()
-
删除
db.session.delete(user)
db.session.commit()
查询方式
主键查询
receive_user=User.query.filter_by(username=recepient).first_or_404()
receive_user=User.query.get_or_404(id)
其他条件查询:
receive_user=User.query.filter_by(username=recepient).first()
多对多的关系
两个表对一个辅助表建立外键索引,然后根据约束来查找
比如:
class 和student 再建立一张表:sdu_class, 表中的属分别和两个子表建立索引关系
具体实现
通过 两个表建立relationship 同时在relationship中声明secondary实现many to many
自引用
上面的学生和老师有两个实体
如果是用户和用户之间,那么只有一个关系。
一个实体之间的多对多称为自引用
给用户添加两个属性
follower和followed
要额外储存两个实体间的额外信息,比如关注者的关注日期
建立一个新的表,同时在user中添加,followed和follwer 和follow表建立自引用
lazy
lazy决定了什么时候从数据库中加载数据
lazy的三个参数
dynamic joined select
dynamic 应该是relationship被调用时返回一个query()对象
joined relationship被调用时返回两个表join的结果值
select 是访问到属性时,会加载全部的属性。
backref
第一个字段是表的名称,第二个字段是给第一个字段表中调用的别名
post=db.relationship('Post',backref='author',lazy='dynamic)
backref提供反向声明
user和post形成一对多的关系,一个user可以生产多个post
Post在找user时可以根据author直接找到,而不需要再根据author_id在User中查找
back_populates
和backref的不同点,在于back_populates是显式声明了关系的应用
版本迁移 迁移脚本中的函数
- current 查看当前版本的信息
- upgrade upgrade() 函数把迁移中的改动应用到数据库中
- downgrade() 函数则将改动 删除。
回退
数据库根据 migrations 回退的方法
downgrade后删除一个版本的函数就好
- 增加用户
db.session.add(user)
db shell
from app.models import *
Role.insert_roles()
date
date.utcnow 没有括号,default可以接受函数作为参数
连接mysql
dir=mysql://root@localhost:3306/hello_flask?charset=utf8mb4
解决bug
-
配置名必须大写...
-
TypeError: init() takes at most 2 arguments (3 given) form表格中没有添加路径
-
书中的bug
在User model中的is_following(user)方法中,检查当前用户是否关注了user用户,应该用self.followed.filter_by(follower=user)
self.followed为current_user关注的所有人的集合,在这个集合中,所有的followed项都为current_user,所有的follower项为各个被关注的用户,所以要找出是否关注了某个用户,应该是,
self.followed.filter_by(follower=user)
View
bootstrap
<div class="col-md-4">
限制了内容的长度
jinjia
safe 标签,告诉jinjia 不要转义html标签
问题
为什么在分页筛选中得不到自己关注自己的那个结果
解决:在follower.html中设置一下。。
评论
一个文章有多个评论。属于一对多关系
一个用户能发表多个评论,然后文章可以有多个评论
在user和post中添加relationship和comment关联
在文章的Index页面每篇文章下显示comment数量,
然后点击可以进入查看post发表文章的主界面,
在主界面下有文章的所有评论,评论的发表时间以及,详细日期
restful api
- 因为是无状态的,又要经过客户端验证,所以用http-auth
进行httpie测试
http --json --auth fjl2401@163.com:cat GET http://127.0.0.1:5000/api/posts
-
创建文章
http --auth fjl2401@163.com:cat --json POST http://127.0.0.1:5000/api/posts/ "body=I'm adding a post from the command line."
-
使用认证令牌来发送请求
http --auth fjl2401@163.com:cat --json GET http://127.0.0.1:5000/api/v1.0/token
在model中的生成令牌的方法:
s = Serializer(current_app.config['SECRET_KEY'],expires_in=expiration)return s.dumps({'id': self.id}).decode('ascii')
http --json --auth eyJhbGciOiJIUzI1NiIsImV4cCI6MTUyNjk1MjM4MCwiaWF0IjoxNTI2OTQ4NzgwfQ.eyJpZCI6MX0.K97b2cUxBU4cqCI63IwFCgzYpMVMre7BRfqXP-6yVmU: GET http://127.0.0.1:5000/api/v1.0/posts/
g.current_user
g.current_user的初始化在before_request那里
所以在请求api时会出现错误是因为在初始化时没有加入before_request()
http状态码
200 响应成功
201 创建成功
202 接受
301 Moved Permanently 永久的移动了
项目部署 和环境
https://icecola.herokuapp.com/ | https://git.heroku.com/icecola.git
运行
python hello.py runserver --host 0.0.0.0
连接数据库:
musql :mysql://username:password@localhost/database
postgresql://username:password@localhost/database
sqlite : sqlite:///absolute/path/to/databse
部署gunicorn
将flask部署在5001端口上
gunicorn -b 0.0.0.0:5001 -w 4 manage:app
gunicorn -b localhost:5000 -w 4 hello_flask:app
supervisor 来部署gunicorn
supervisor 进程管理工具
~/django_1/VENV_flask/hello_flask
利用supervisor来监视进程,在进程死亡后拉起
sudo mkdir /etc/supervisor
sudo echo_supervisord_conf > /etc/supervisord.conf 生成配置文件
查看默认配置文件
查看supervisord是否在运行
ps aux | grep supervisord
echo_supervisord_conf
- 启动服务
supervisord -c /etc/supervisor/supervisord.conf
运行自己写的配置文件:
supervisord -c /etc/supervisor/conf.d/hello_flask.conf
使用 supervisorctl
Supervisorctl 是 supervisord 的一个命令行客户端工具,启动时需要指定与 supervisord 使用同一份配置文件,否则与 supervisord 一样按照顺序查找配置文件。
进入客户端
supervisorctl -c /etc/supervisord.conf
supervisorctl -c /etc/supervisor/conf.d/hello_flask.conf
-
进入之后
- status 查看状态
- reread 重新启动
- restart program_name 重启某个项目
- stop all 停止所有
- update 更新
-
启动某个进程
supervisorctl starprogram_name
重新加载
supervisorctl reload
配置了http服务后的启动 sudo supervisorctl -u chris -p 123
-
gunicorn直接启动命令
/home/ubuntu/django_1/VENV_flask/bin/gunicorn -b localhost:5000 -w 4 hello_flask:app
conf文件的配置
[program:hello_flask ]
command=/home/ubuntu/django_1/VENV_flask/bin/gunicorn -b localhost:5000 -w 4 hello_flask:app
directory=/home/ubuntu/django_1/VENV_flask/hello_flask
user=ubuntu
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stdout_logfile=/home/ubuntu/django_1/VENV_flask/hello_flask/log/out_log.log
stderr_logfile=/home/ubuntu/django_1/VENV_flask/hello_flask/log/err_log.log
[supervisord]
575报错:
关联到nginx
location {
# forward application requests to the gunicorn server
proxy_pass http://localhost:5000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static {
# handle static files directly, without forwarding to the application
alias /home/ubuntu/django_1/VENV_flask/hello_flask/app/static;
expires 30d;
}
# write access and error logs to /var/log
access_log /var/log/hello_flask_access.log;
error_log /var/log/hello_flask_error.log;
}
部署到heroku
登录
heroku login
输入在官网注册的账户和 密码
创建应用
heroku create <appname> 名字可以自己选
heroku create --buildpack heroku/python
Creating heroku-postgresql on ⬢ floating-ravine-41608... free
https://floating-ravine-41608.herokuapp.com/ | https://git.heroku.com/floating-ravine-41608.git
添加数据库
heroku addons:create heroku-postgresql -a floating-ravine-41608
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-parallel-90394 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
显示git和网页地址
git remote show heroku
进入数据库
heroku pg:psql -a floating-ravine-41608
如果运行git push heroku出错,运行这步
heroku git:remote -a floating-ravine-41608
push上去 即可运行
git push heroku master
部署flask 需要的文件
runtime.txt 表示运行时的python环境
python-2.7.14
requirements.txt 用 pip freeze > requirements.txt 生成
Flask==0.10.1
Flask-SQLAlchemy==2.0
Flask-WTF==0.12
Jinja2==2.8
MarkupSafe==0.23
SQLAlchemy==1.0.8
WTForms==2.0.2
Werkzeug==0.10.4
click==4.1
decorator==4.0.2
geocoder==1.4.1
gunicorn==19.3.0
itsdangerous==0.24
psycopg2==2.7.3.1
ratelim==0.1.6
requests==2.7.0
six==1.9.0
wsgiref==0.1.2
Procfile 显示路由的app和 运行的服务器
web: gunicorn routes:app
.env.env 配置环境变量 ,heroku会直接从这里读取
FLASK_APP=flasky.py
FLASK_CONFIG=heroku
MAIL_USERNAME=fjl2401
MAIL_PASSWORD=youpass
添加后台任务
在views中用current_user来调用,实现跳转到models的方法
用rq来处理,在models.user中定义launch方法,实现跳转到current_app的队列
然后通过rq_job这个名字自动加入到消息队列中,任务的名称为name+user.id
在app的init中 任务队列初始化,连接到redis服务器
在当前目录下找tasks这个文件,然后根据 views传进来的名称来定位到对于的目录下的py文件的函数名,
运行对应的函数。
在指定name任务队列中分配worker,保证该任务队列有worker
然后实例化一个队列对象
queue = rq.Queue('new_work', connection=Redis.from_url('redis://'))
将方法添加到队列生成job
job = queue.enqueue('app.tasks.example', 23)
rabbit-rmq
地址
用户名和密码 guest
启动: ./rabbitmq-server
ps -ef|grep rabbit
网友评论