[Flask教程] 6.ORM与SQLAlchemy (2) – 模型关系与引用

承接上文,我们的Q&A demo,除了用户表,还需要存储所有问题内容的表questions_info和存储所有评论的表comments_info,并且都和users_info通过外键来关联。我们不排除后续需要更多表的可能性,把所有模型和视图函数写在一起看着也太混乱了!为此,我们新建一个models.py,把三个模型都放在这里。

由于是新建的models.py文件,我们同样要先在开头生成一个名为db的SQLAlchemy对象:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

前文中我们给SQLAlchemy传入了Flask对象app作为参数,这里是不是也要从视图函数文件HarpQA.py导入那个app并传进去呢?并不可以,因为HarpQA.py也要使用到db(如db.session),这样就产生了循环引用,所以在这里不能传入app,而是回到HarpQA.py,使用db.init_app(app)将app和db绑定,避免了循环引用。

users_info表(Users模型)代码如下:

class Users(db.Model):
    __tablename__ = 'users_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(32), nullable=False)
    password = db.Column(db.String(100), nullable=False)
    register_time = db.Column(db.DateTime, nullable=False, default=datetime.now())
    # 我们新增了一个avatar_path字段来存用户头像图片文件的路径
    avatar_path = db.Column(db.String(256), nullable=False, default='images/doraemon.jpg')    

questions_info表(Questions模型)代码如下:

class Questions(db.Model):
    __tablename__ = 'questions_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.TEXT, nullable=False)
    author_id = db.Column(db.Integer, db.ForeignKey('users_info.id'))
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.now())

    author = db.relationship('Users', backref=db.backref('questions', order_by=create_time.desc()))

这个表存储所有问题的标题、内容、创建时间、作者ID,作者ID通过外键与用户表的ID关联,方式也很简单,在db.Column中用db.ForeignKey(‘users_info.id’)作为参数即可。
再看最后一条语句:

author = db.relationship('Users', backref=db.backref('questions', order_by=create_time.desc()))

db.relationship会自动找到两个表的外键,建立Questions和Users的关系,此时对于任意一个Questions对象question,通过question.author就可获得这个question的作者对应的Users对象,例如获取id为1的问题的作者姓名:

question = Questions.query.filter(Questions.id == 1).first()
author_name = question.author.username

db.relationship的第二个参数backref=db.backref(‘questions’, order_by=create_time.desc())则建立了一个反向引用,这样我们不仅可以使用question.author,还可以使用author.questions获得一个作者所有的问题,并通过order_by=create_time.desc()按创建时间倒序排列(网页的内容按时间倒序排列),返回的是一个Questions对象的列表,可以遍历它获取每个对象,如获取作者Harp的所有问题的title:

author = Users.query.filter(Users.username == 'Harp').first()
for question in author.questions:
    print(question.title)

同理,comments_info表(Comments模型)代码如下:

class Comments(db.Model):
    __tablename__ = 'comments_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    content = db.Column(db.TEXT, nullable=False)
    question_id = db.Column(db.Integer, db.ForeignKey('questions_info.id'))
    author_id = db.Column(db.Integer, db.ForeignKey('users_info.id'))
    create_time = db.Column(db.DateTime, nullable=False, default=datetime.now())

    author = db.relationship('Users', backref=db.backref('comments'))
    question = db.relationship('Questions', backref=db.backref('comments', order_by=create_time.desc()))

在HarpQA.py中,我们要从models.py导入db及所有的模型,注意因为上下文的关系,我们这里用with语句把app推入栈中:

from flask import Flask, render_template
from models import db, Users, Questions, Comments

import config

app = Flask(__name__)
app.config.from_object(config)

db.init_app(app)

with app.test_request_context():
    db.drop_all()
    db.create_all()


@app.route('/')
def index():
    return render_template('home.html')


if __name__ == '__main__':
    app.run()

运行脚本,此时数据库已经把三张表都建立好了:

未分类

[Flask教程] 5.ORM与SQLAlchemy (1) – 建立第一个模型

后端一个重要的点就是与数据库联系,例如网页的注册、登录,内容的更新等都需要与数据库建立关系。以MySQL数据库为例,平时我们会用mysqldb(python 2)或者pymysql(python 3)去操作MySQL数据库,但这种方法也是需要自己编写SQL语句的。现在我们有了ORM模型,简单来说,ORM是把数据库中的表抽象成模型,表的列名对应模型的属性,这样我们可以调用类的属性或方法去获得数据库中的数据。例如假设MySQL数据库中有一张表名为table1,使用SELECT * FROM table1 WHERE id=1获取id为1的数据,如果将表table1映射成ORM模型Table,那么可以直接使用Table.query.filter(id=1),这样操作简单了很多,也很利于理解。

SQLAlchemy就是一个这样的ORM,我们可以直接安装flask_sqlalchemy来使用。在这之前我们先在MySQL中手动建立一个数据库harp,在建立的时候把charset设置为utf8,避免存入中文时变成乱码,然后在配置文件config.py中填写好数据库的连接信息:

HOST = "127.0.0.1"
PORT = "3306"
DB = "harp"
USER = "root"
PASS = "Your Password"
CHARSET = "utf8"
DB_URI = "mysql+pymysql://{}:{}@{}:{}/{}?charset={}".format(USER, PASS, HOST, PORT, DB, CHARSET)
SQLALCHEMY_DATABASE_URI = DB_URI

SQLAlchemy依赖mysqldb或者pymysql去连接数据库和执行SQL语句,因为我们用的是python 3,所以需要在配置信息中指明使用pymysql,如果是python 2可以省略,默认是使用mysqldb。

建立好了数据库,我们开始建表,首先建立一张用户表,我们设想它应该有id(作为主键)、用户名、密码、注册时间这些基本的字段,有了ORM,我们就不用再写SQL去建表了,在项目的主py文件中添加以下代码:

from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

import config

app = Flask(__name__)
app.config.from_object(config)

db = SQLAlchemy(app)


class Users(db.Model):
    __tablename__ = 'users_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(32), nullable=False)
    password = db.Column(db.String(100), nullable=False)
    register_time = db.Column(db.DateTime, nullable=False, default=datetime.now())


db.create_all()

解读一下这段代码,导入SQLAlchemy和含有数据库连接信息的config,实例化一个SQLAlchemy对象名为db,其传入的参数为Flask实例app。接下来定义了一个User类,这个类就是ORM中的模型,也就是数据库中的表映射的模型,它需要继承自db.Model,tablename这个属性就是建表后,数据库生成的表名;然后使用db.Column来实例化id/username/password/register_time这几个列,db.Column的参数描述列的类型、主键等信息,如db.Integer/db.String(32)/db.DateTime分别代表整形、字符串(最大长度)、时间,primary_key=True说明该字段为主键,autoincrement=True代表自增长,nullable决定是否可为空,default代表默认值。最后用db.create_all()来实现创建。我们暂时不用理解为何SQLAlchemy需要传入Flask实例作为参数,为何模型要继承自db.Model,重要的是可以先把想要的表建立起来。

进入数据库,输入desc user_info;,我们发现表已经建立好了,其结构图如下:

未分类

但它现在还是空的,我们来试着插入一条语句,将视图函数修改为:

@app.route('/')
def index():
    user = Users(username='Harp', password='123456')
    db.session.add(user)
    db.session.commit()
    return render_template('home.html')

代码实例化一个Users的对象user,传入username和password,使用db.session.add(user)将其加入到数据库的session(可以理解为事务)中,然后使用db.session.commit()提交。我们运行程序,然后用浏览器访问,浏览器正常显示了结果,这时再看一眼数据库,发现这条数据已经写入到了数据库:

未分类

查询、修改数据也同样很简单:

@app.route('/')
def index():
    user = Users.query.filter(Users.id == 1).first()    #查找
    print(user.username)
    user.username = 'Harp1207'    #修改
    db.session.commit()    #修改后需提交
    print(user.username)
    return render_template('home.html')

思考问题:

  1. 为何要把模型的操作语句放在视图函数中?(搜索上下文这个概念)
  2. 数据查找,我们用的是Model.query,其实还可以用db.session.query,两者有何区别?filter和filter_by又有何区别?

[Flask教程] 4.模板的继承及Bootstrap实现导航条

在建设一个网站的时候,不同的页面有很多元素是一样的,比如导航条、侧边栏等,我们可以使用模板的继承,避免重复编写html代码。现在我们打算实现一个在网页上方的导航条,并在所有的页面中继承这个导航条。导航条的建立,我们直接使用Bootstrap提供的如下导航条的样式。

未分类

但在使用Bootstrap的导航条样式之前,需要先引用Bootstrap所需要的css和js文件以及jQuery,我们在html的header中插入以下代码完成引用:

<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous">

这里都是通过链接引用网络上的css和js文件,而不是将其下载下来并从本地引用。之后我们把上图中的所有html代码复制到html的body中,浏览器就能显示和图中一样的导航条了。我们再做一些简单的修改和优化,最终我们的导航条是这样的:

未分类

具体修改的点是,我把原始的Brand替换成了一个图片作为logo,第一个下拉控件Dropdown删掉了,最右侧的下拉控件增加了一个选项,并把文字都替换成了我们想要的。然后建立了一个base.css文件来调整图片大小、控件位置、背景色等等,这一部分都是基础的html/css知识,也就不多说。后续所有的网页都要使用这个导航条,我们将含有导航条这个html命名为base.html,并在其body中,导航条代码的下方增加以下代码:

{% block body_part %}
{% endblock %}

然后新建一个home.html,输入以下内容:

{% extends 'base.html' %}
{% block body_part %}
<p>This is body content under nav-bar</p>
{% endblock %}

渲染home.html并访问,我们可以看到这样的结果:

未分类

因此我们不难理解,在home.html中,{% extends ‘base.html’ %}表示继承自base.html,home.html中block和endblock区间的代码块会自动替换到base.html同样名为body_part的block区域。block可以使用多个,例如在中也可以使用,以便于不同的页面设置不同的标题。<br /> 最终base.html代码如下:</p> <pre><code><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <link rel="stylesheet" href="{{ url_for('static',filename='css/base.css') }}"/> <link rel="shortcut icon" href="{{ url_for('static', filename='images/favicon.ico') }}"> <title>{% block page_name %}{% endblock %}-HarpQA</title> </head> <body> <nav class="navbar navbar-default"> <div class="container"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand"> <img class="logo" src="{{ url_for('static',filename='images/logo.png') }}"> </a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">首页 <span class="sr-only">(current)</span></a></li> <li><a href="#">发布问答</a></li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Key Words"> </div> <button type="submit" class="btn btn-default">搜索</button> </form> <ul class="nav navbar-nav navbar-right"> <li><a href="#">登录</a></li> <li><a href="#">注册</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">友情链接<span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="mailto:liutao25@baidu.com">联系我</a></li> <li><a href="http://flask.pocoo.org" target="_blank">Flask官网</a></li> <li><a href="https://www.python.org/">Python官网</a></li> <li role="separator" class="divider"></li> <li><a href="https://www.baidu.com" target="_blank">百度搜索</a></li> <li role="separator" class="divider"></li> <li><a href="https://www.google.com.hk" target="_blank">Google Search</a></li> </ul> </li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> {% block body_part %} {% endblock %} </body> </html> </code></pre> <p>请注意一下base.css和logo图片的引用,我们也使用了url_for函数,第一个参数是static,代表项目文件夹下static文件夹,第二个参数是以static文件夹为基准静态文件的相对路径,我们把js文件/css文件/图片文件等都放在这个文件夹下,所以这个用法以后会经常使用。我们在收藏网页的时候,网页都有一个小图标,我们也可以在header中使用这行html代码来实现:</p> <pre><code><link rel="shortcut icon" href="{{ url_for('static', filename='images/favicon.ico') }}"> </code></pre> <p>把favicon.ico文件放在static/images文件夹即可,我们使用了Flask的图标,效果如下图:</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-4176820475cb8028cba5de1555563148.png" alt="未分类" width="120" height="33" class="aligncenter size-full wp-image-7238" /></p> <p>base.css代码如下:</p> <pre><code>body{ background: #F3F3F3; } .navbar-brand{ padding: 0 5px; padding-right: 10px; } .logo{ height: 50px; } </code></pre> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2018/01/flask-tutorial-4-inheritance-of-template-and-bootstrap-implementation-of-navigation-bar/" rel="bookmark"><time class="entry-date published updated" datetime="2018-01-18T15:31:30+08:00">2018年1月18日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/bootstrap/" rel="tag">Bootstrap</a>、<a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <article id="post-7234" class="post-7234 post type-post status-publish format-standard hentry category-undefine tag-flask tag-jinja2 tag-render_template"> <header class="entry-header"> <h2 class="entry-title"><a href="https://devops.webres.wang/2018/01/flask-tutorial-3-render-template-rendering-template-and-jinja2/" rel="bookmark">[Flask教程] 3.render_template渲染模板及jinja2</a></h2> </header><!-- .entry-header --> <div class="entry-content"> <p>我们之前的视图函数,返回的都是简单的’Hello Wolrd’之类的字符串,怎么返回一个html呢?首先我们在templates文件夹建立一个html文件,内容随便写一点如下:</p> <pre><code><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <h1>This is index page</h1> </body> </html> </code></pre> <p>我们可以使用Flask对象app的send_static_file方法,使视图函数返回一个静态的html文件,但现在我们不使用这种方法,而是使用flask的render_template函数,它功能更强大。<br /> 从flask中导入render_template,整体代码如下:</p> <pre><code>from flask import Flask, render_template import config app = Flask(__name__) app.config.from_object(config) @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run() </code></pre> <p>render_template函数会自动在templates文件夹中找到对应的html,因此我们不用写完整的html文件路径。用浏览器访问’/’这个地址,显示结果如下:</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-9530293985cb802888c8b71555563144.png" alt="未分类" width="316" height="142" class="aligncenter size-full wp-image-7230" /></p> <p>那么为何称之为模板呢?因为render_template不仅能渲染静态的html文件,也能传递参数给html,使一个html模板根据参数的不同显示不同的内容,这是因为flask使用了jinja2这个模板引擎。要使用模板,在render_template参数中以key=value形式传入变量,在html中使用{{key}}来显示传入的变量,例如:</p> <pre><code># 视图函数 @app.route('/') def index(): return render_template('index.html', contents='This is index page') # html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <h1>{{ contents }}</h1> </body> </html> </code></pre> <p>浏览器显示的结果与上文是一样的。我们还可以直接把一个类的实例传递过去,并在模板中访问类的属性,例如假设一个类对象obj有a和b属性,关键部分的代码如下:</p> <pre><code># 视图函数中 return render_template('index.html', object=obj) ... # html中 <p>a: {{ object.a }}</p> <p>b: {{ object.b }}</p> </code></pre> <p>传入一个字典也可以,并且在模板中既可以用dict[key],也可以用dict.key。</p> <p>使用过滤器,可以在html中对传入的变量进行处理,其格式是{{ 变量 | 过滤器 }},例如将前文的{{ contents }}修改为{{ contents | upper}},浏览器显示的内容就变成了:</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-20056848865cb80288d576a1555563144.png" alt="未分类" width="344" height="139" class="aligncenter size-full wp-image-7231" /></p> <p>所以我们可以很容易就理解,过滤器其实就是以变量为参数的函数,返回处理后的结果,在后端一样可以先用字符串对象的upper()函数处理好再传递给模板,效果是完全一样的。jinja2自带了一些过滤器,例如length/reverse/lower等等 ,并且我们也可以自己按照需求自定义过滤器,模板还支持{{ 变量 | 过滤器1 | 过滤器2 | … }}这样的操作。想要深入了解的话,可以搜索jinja2过滤器去进一步学习。</p> <p>模板中还可以使用if else和for in控制语句,与变量使用{{ }}不同,控制语句要放在{% %}里,例如前文的contents传入一个list:</p> <pre><code>contents=[i for i in range(10)] </code></pre> <p>html中代码如下:</p> <pre><code><h1> {% for i in contents %} {{ i }}{# 注意i也要用两个大括号 #} {% endfor %} </h1> </code></pre> <p>使用for遍历contents的内容,并用{{ i }}显示出来,同时还用{# #}加了一个注释,还要注意需要使用{% endfor %}来提示循环区域的结束,因为html不像python那样通过缩进来判断循环的区域,if也是同理。浏览器显示结果:</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-3683356425cb80289270de1555563145.png" alt="未分类" width="310" height="136" class="aligncenter size-full wp-image-7232" /></p> <p>最后for和if结合使用:</p> <pre><code><h1>header</h1> {% for i in contents %} <p> {% if i%2 == 0 %} {{ i }}是偶数{# 注意i也要用两个大括号 #} {% else %} {{ i }}是奇数 {% endif %} </p> {% endfor %} </code></pre> <p>结果如下(只是演示一下,不要吐槽排版和美观):</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-3958481005cb802896cd471555563145.png" alt="未分类" width="256" height="333" class="aligncenter size-full wp-image-7233" /></p> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2018/01/flask-tutorial-3-render-template-rendering-template-and-jinja2/" rel="bookmark"><time class="entry-date published updated" datetime="2018-01-18T15:28:43+08:00">2018年1月18日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a>、<a href="https://devops.webres.wang/tag/jinja2/" rel="tag">jinja2</a>、<a href="https://devops.webres.wang/tag/render_template/" rel="tag">render_template</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <article id="post-7229" class="post-7229 post type-post status-publish format-standard hentry category-undefine tag-flask tag-redirect tag-url_for"> <header class="entry-header"> <h2 class="entry-title"><a href="https://devops.webres.wang/2018/01/flask-tutorial-2-reversal-function-url-for-and-redirect/" rel="bookmark">[Flask教程] 2.反转函数url_for与重定向redirect</a></h2> </header><!-- .entry-header --> <div class="entry-content"> <p>在flask中,我们导入url_for和redirect两个函数。</p> <pre><code>from flask import Flask, url_for, redirect </code></pre> <p>首先看url_for,简单来说,这个函数接受视图函数的名字(字符串形式)作为参数,返回视图函数对应的url,例如:</p> <pre><code>@app.route('/') def hello_world(): print(url_for('index')) return 'Hello World' @app.route('/index/') def index(): return 'index' </code></pre> <p>在hello_world函数中使用print(url_for(‘index’)),将会打印出/index/。<br /> 有传参的视图函数怎么办?同样将函数名字符串作为第一个参数,将参数以key=value的形式写在后面,如:</p> <pre><code>@app.route('/') def hello_world(): print(url_for('hello',name='harp')) return 'Hello World' @app.route('/<name>/') def hello(name): return 'Hello %s' % name </code></pre> <p>打印结果为/harp/。</p> <p>redirect则更简单,功能就是跳转到指定的url,大部分情况下,我们都是和url_for一起使用的,例如:</p> <pre><code>@app.route('/') def hello_world(): return 'Hello World' @app.route('/<name>/') def hello(name): if name == 'Harp': return 'Hello %s' % name else: return redirect(url_for('hello_world')) </code></pre> <p>在hello这个视图函数中,如果url传入的参数是Harp(即请求的网址是http://127.0.0.1:5000/Harp/),则返回’Hello Harp’,其他情况则重定向到hello_world这个视图函数对应的网址’/’。</p> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2018/01/flask-tutorial-2-reversal-function-url-for-and-redirect/" rel="bookmark"><time class="entry-date published updated" datetime="2018-01-18T15:25:04+08:00">2018年1月18日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a>、<a href="https://devops.webres.wang/tag/redirect/" rel="tag">redirect</a>、<a href="https://devops.webres.wang/tag/url_for/" rel="tag">url_for</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <article id="post-7228" class="post-7228 post type-post status-publish format-standard hentry category-undefine tag-debug tag-flask tag-url"> <header class="entry-header"> <h2 class="entry-title"><a href="https://devops.webres.wang/2018/01/flask-tutorial-1-the-first-flask-program-debug-mode-and-url-reference/" rel="bookmark">[Flask教程] 1.第一个Flask程序、DEBUG模式及URL传参</a></h2> </header><!-- .entry-header --> <div class="entry-content"> <p>–引言请见我的简书https://www.jianshu.com/p/e27…</p> <p>我们打开PyCharm,新建一个Project,在左侧选择Flask,右侧Location可选择项目存放的位置,Project Interpreter选择使用的编译器,我们当然可以选择已经安装在电脑上的python 3.6,但我建议选择使用Virtualenv来建立一个虚拟的环境,这样在虚拟环境里安装包之类的,不会影响电脑上本身已经装的,并且后续如果项目较多的话也便于区分和管理。</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-9640389665cb802846d3a31555563140.png" alt="未分类" width="554" height="348" class="aligncenter size-full wp-image-7226" /></p> <p>点击Create,创建好之后,pycharm自动在项目的文件夹下建立了static/templates文件夹和HarpQA.py,此外还有一个venv文件夹,是虚拟环境用的,我们可以暂时不用管它。(如果你的PyCharm是社区版的,那么无法像专业版那样建立Flask项目,对应的文件夹需要自己手动建立。)HarpQA.py中的代码如下:</p> <pre><code>from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run() </code></pre> <p>首先从flask导入Flask,(我并没有pip install flask,为何能导入呢?我理解是建立flask项目的时候PyCharm自动帮我们做了这个事情),然后初始化一个Flask对象app,参数是<strong>name</strong>,<strong>name</strong>代表的是本身这个模块的名字,我们暂时不用理解为何要传入这个参数。接下来是一个hello world函数,并且有一个装饰器@app.route(‘/’),意思是说,当接收到’/’这个网址的请求时,执行hello world这个函数,即返回字符串’Hello World!’(add_url_rule()也可以实现和@app.route一样的功能 ,但使用装饰器应该更pythonic吧),最后使用app.run()运行。运行脚本后,提示Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)<br /> 我们在浏览器中输入http://127.0.0.1:5000/,就可以在网页中看到’Hello World!’了。</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-216373615cb80284bc3201555563140.png" alt="未分类" width="272" height="101" class="aligncenter size-full wp-image-7227" /></p> <p>我们还可以使用debug模式来运行flask的服务端,开启debug模式后,修改代码不需要关闭程序,可以实时生效。有多种方法来开启debug模式:</p> <ol> <li>在app.run()中添加参数app.run(debug=True);</li> <li>在run之前增加app.debug = True;</li> <li>新建config文件,在config文件中添加DEBUG = True,然后在程序中引入app.config.from_object(config);</li> <li>在run之前增加app.config[‘DEBUG’] = True;</li> </ol> <p>我们使用第3种方法,新建一个独立的文件来保存各种参数,以后项目增大的时候参数增多,便于管理。同样在项目文件夹下新建config.py文件,添加DEBUG = True,DEBUG需大写。在HarpQA.py中import config并添加app.config.from_object(config),再次运行HarpQA.py,提示:</p> <pre><code> * Restarting with stat * Debugger is active! * Debugger PIN: 229-291-890 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) </code></pre> <p>说明Debug模式已经打开,我们可以直接修改代码,例如把’Hello World!’<br /> 修改成’Hello’并保存,显示提示:</p> <pre><code> * Detected change in 'D:\Flask\HarpQA\HarpQA.py', reloading * Restarting with stat * Debugger is active! * Debugger PIN: 229-291-890 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) </code></pre> <p>说明DEBUG模式检测到了代码的变化并自动重载了,这时候刷新网页,结果也变成了只显示’Hello’。</p> <p>如果我们要处理很多URL,可以一个一个去给他们写对应的视图函数,这有时候是不切实际的,我们有更高效的做法,如以下代码:</p> <pre><code>@app.route('/<name>/') def hello(name): return 'Hello %s' % name </code></pre> <p>@app.route中装饰的地址是’//’,注意name外面用了尖括号,代表name是一个参数,例如我们请求http://127.0.0.1:5000/Harp/这个网址,其中的name就是Harp,这个参数会传递给视图函数hello,最终会返回’Hello Harp’。我们可以给将写成,表示传入的参数是字符串类型的,其他的类型还有int,float,path,几种类型的区别可以在网上搜索对比一下,例如path可以将参数里的’/’也传递过来。</p> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2018/01/flask-tutorial-1-the-first-flask-program-debug-mode-and-url-reference/" rel="bookmark"><time class="entry-date published updated" datetime="2018-01-18T15:22:23+08:00">2018年1月18日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/debug/" rel="tag">DEBUG</a>、<a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a>、<a href="https://devops.webres.wang/tag/url/" rel="tag">URL</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <article id="post-6954" class="post-6954 post type-post status-publish format-standard hentry category-undefine tag-flask tag-nginx-2 tag-python3 tag-supervisor tag-ubuntu tag-uwsgi"> <header class="entry-header"> <h2 class="entry-title"><a href="https://devops.webres.wang/2018/01/ubuntu-deployment-python-3-flask-nginx-uwsgi-supervisor-perfect/" rel="bookmark">Ubuntu部署python3-flask-nginx-uwsgi-supervisor完美</a></h2> </header><!-- .entry-header --> <div class="entry-content"> <h2>安装虚拟环境</h2> <pre><code>$ pip install virtualenv $ pip install virtualenvwrapper </code></pre> <h3>把虚拟机环境添加环境变量中</h3> <p>这个最好find / -name virtualenvwrapper.sh 看下位置</p> <pre><code>$ vi .bashrc if [ -f /usr/local/bin/virtualenvwrapper.sh ]; then export WORKON_HOME=$HOME/.virtualenvs source /usr/local/bin/virtualenvwrapper.sh fi </code></pre> <h3>为flask项目创建一个虚拟环境</h3> <pre><code>$ mkvirtualenv --python=python3 flask #flask这个名字可以按自己需求修改,我项目是需要python3。所以选择 --python=python3,如果需要python2可以不加这个。 $ deactivate #安装完虚拟环境后,先退出这个虚拟环境。 </code></pre> <h2>安装mysql数据库,安装数据这个没什么好提的网上有很多详细教程</h2> <pre><code>$ apt install mysql-server mysql-client $ apt install libmysqld-dev </code></pre> <h2>安装nginx</h2> <pre><code>$ apt install nginx #这个安装也比较简单 </code></pre> <h2>安装supervisor</h2> <p>需要是python2 暂时不支持python3,这里有时候会遇到坑。pip install –upgrade pip 看看现在pip是什么版本。</p> <pre><code>$ vi /usr/local/bin/pip #如果发现pip是python3,不要慌用这个命令把第一行的python3修改python2 即可,如果是python2就无视这步 $ pip install supervisor #安装supervisor </code></pre> <h2>安装uwsgi</h2> <p>需要注意flask项目需要的环境 选择python3 还是python2.</p> <p>这个我的项目是python3,如果是python2创建虚拟环境就用python2。具体可以看上面的为项目创建虚拟环境</p> <pre><code>$ workon flask #进入虚拟环境 $ pip install uwsgi #这个之前装到虚拟环境里面 </code></pre> <p>如果出现Failed building wheel for uwsgi执行下面语句</p> <pre><code>apt-get install python3-dev </code></pre> <h2>项目文件创建</h2> <p>这个按自己需要创建,也可以按我这个创建</p> <pre><code>$ mkdir /www #根目录下创建一个www $ mkdir /www/wwwroot #这个项目文件全部放这个理 $ mkdir /www/log #日志文件 </code></pre> <h2>uwsgi配置</h2> <p>uwsgi配置好后,可以测试下</p> <p>uwsgi配置路径:/www/wwwroot/uwsgi.ini</p> <pre><code>$ cd /www/wwwroot #可以放到项目,按自己需求都可以 $ vi uwsgi.ini #创建一个uwsgi配置文件 [uwsgi] # 当前这个项目的路径 chdir = /www/wwwroot # 模块 module = manage #启动文件名 个人理解 # python的虚拟环境 home = /root/.virtualenvs/python3 # 是否启用mater模式 master = true # 进程数 processes = 2 # socket文件地址 socket = /www/wwwroot/uwsgi.sock # wsgi文件 wsgi-file = /www/wwwroot/manage.py #启动文件 # wsgi文件中的app变量 callable = app # socket文件的权限 chmod-socket = 666 </code></pre> <p>配置好后可以运行起来测试是否成功</p> <pre><code>$ workon python3 #进入虚拟环境 $ uwsgi --uid www --gid www --ini /www/wwwroot/uwsgi.ini #这个可以指定用户和用户组权限,也可以不指定。测试没能正常打开项目就往下面步骤继续配置 </code></pre> <h2>nginx配置</h2> <pre><code>$ cd /etc/nginx/sites-enabled/ #切换到nginx默认配置目录 $ mv default default.bak #修改配置前先备份下配置 $ vi default server { listen 80; server_name www.xxoo.com; charset utf-8; client_max_body_size 75M; access_log /www/log/xxoo.access.log; error_log /www/log/xxoo.error.log; location / { include uwsgi_params; uwsgi_pass unix:/www/wwwroot/uwsgi.sock; #这个.sock文件一定要和uwsgi配置中一样 } } </code></pre> <p>修改nginx配置/etc/nginx/nginx.conf ,第一行user www www ; Nginx用户及组:用户 组 按自己需求配置。详细配置参数网上自己找</p> <h2>supervisor配置</h2> <p>supervisor配置路径:/www/wwwroot/supervisor.conf</p> <pre><code>$ vi supervisor.conf [program:python] #这个python可以按自己需求写 # supervisor执行的命令 command=/root/.virtualenvs/py3-zqcms/bin/uwsgi --uid www --gid www --ini /www/wwwroot/uwsgi.ini # 项目的目录 directory = /www/wwwroot # 开始的时候等待多少秒 startsecs= 5 #按自己需求写 # 停止的时候等待多少秒 stopwaitsecs= 5 #按自己需求写 # 自动开始 autostart=true # 程序挂了后自动重启 autorestart=true # 输出的log文件 stdout_logfile=/www/log/supervisord.log # 输出的错误文件 stderr_logfile=/www/log/supervisorderr.log [supervisord] # log的级别 loglevel=info </code></pre> <p>配置好后就运行</p> <pre><code>$ supervisord -c /www/wwwroot/supervisor.conf #执行的时候注意是在python2环境 </code></pre> <p>如何终止多余 Supervisor 进程?</p> <pre><code>$ ps -ef | grep supervisor #查看 $ kill 4012 #结束进程 </code></pre> <p><strong>注意:uwsgi nginx supervisor我只是简单配置了下,各位可以按需求配置。详细配置参数网上有很多资料。哪里写错,可以留言告诉我。</strong></p> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2018/01/ubuntu-deployment-python-3-flask-nginx-uwsgi-supervisor-perfect/" rel="bookmark"><time class="entry-date published updated" datetime="2018-01-13T15:38:33+08:00">2018年1月13日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a>、<a href="https://devops.webres.wang/tag/nginx-2/" rel="tag">nginx</a>、<a href="https://devops.webres.wang/tag/python3/" rel="tag">python3</a>、<a href="https://devops.webres.wang/tag/supervisor/" rel="tag">supervisor</a>、<a href="https://devops.webres.wang/tag/ubuntu/" rel="tag">ubuntu</a>、<a href="https://devops.webres.wang/tag/uwsgi/" rel="tag">uWSGI</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <article id="post-6917" class="post-6917 post type-post status-publish format-standard hentry category-undefine tag-flask"> <header class="entry-header"> <h2 class="entry-title"><a href="https://devops.webres.wang/2018/01/how-flask-handles-concurrency/" rel="bookmark">flask如何处理并发</a></h2> </header><!-- .entry-header --> <div class="entry-content"> <p><strong>1、使用自身服务器的多进程或者多线程,参考werkzeug的run_simple函数的入参。注意,进程和线程不能同时开启</strong></p> <p><strong>2、使用gunicorn使用多进程,-w worker 进程数,类型于运行多个app.run()开发服务器</strong></p> <pre><code>gunicorn app -w 2 -b :8000 </code></pre> <p><strong>3、使用gevent异步</strong></p> <pre><code>/usr/local/bin/gunicorn -t120 -w10 -b 10.57.17.57:3000 --worker-class gevent Erebus:APP </code></pre> <pre><code>-k STRING, --worker-class STRING The type of workers to use. [sync] -w INT, --workers INT The number of worker processes for handling requests. [1] -t INT, --timeout INT Workers silent for more than this many seconds are killed and restarted. [30] -b ADDRESS, --bind ADDRESS The socket to bind. [['127.0.0.1:8000']] </code></pre> <p>当运行开发服务器时,运行app.run(),你会得到一个单一的同步进程,这意味着一次最多只能处理1个请求。</p> <p>通过在其默认配置中坚持Gunicorn在它的前面,只是增加 – 工作,你获得的本质上是一些进程(由Gunicorn管理),每个行为像app.run()开发服务器。 4个worker == 4个并发请求。这是因为Gunicorn默认使用它包含的同步工作类型。</p> <p>重要的是要注意,Gunicorn还包括异步工作,即eventlet和gevent(和龙卷风,但是最好使用Tornado框架,似乎)。通过使用–worker-class标志指定其中一个异步工作者,您所获得的是Gunicorn管理多个异步进程,每个进程管理自己的并发。这些进程不使用线程,而是协同程序。基本上,在每个进程内,每次只能发生一件事情(1个线程),但是当对象在等待外部进程完成时(可以考虑数据库查询或等待网络I / O),它们可以被“暂停”。</p> <p>这意味着,如果你使用Gunicorn的异步工作者,每个工作者可以一次处理多个请求。只有多少工人是最好的取决于你的应用程序的性质,它的环境,它运行的硬件等等。更多的细节可以在Gunicorn’s design页和notes on how gevent works在其介绍页上找到。</p> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2018/01/how-flask-handles-concurrency/" rel="bookmark"><time class="entry-date published updated" datetime="2018-01-13T14:37:27+08:00">2018年1月13日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <article id="post-6608" class="post-6608 post type-post status-publish format-standard hentry category-undefine tag-flask tag-nginx-2 tag-ubuntu tag-uwsgi"> <header class="entry-header"> <h2 class="entry-title"><a href="https://devops.webres.wang/2017/12/ubuntuuwsginginx-deployment-flask-application/" rel="bookmark">Ubuntu+uwsgi+Nginx部署Flask应用</a></h2> </header><!-- .entry-header --> <div class="entry-content"> <p>由于是第一次在Linux部署Python应用,过程中遇到很多坑,也找了很多部署博客的分享。再一次体会到好文章带你上天堂,坏文章带你瞎逼忙的道理。索性就记录这次部署的全过程,供以后参考。</p> <h2>介绍</h2> <p>首先先介绍下各个技术的功能,以及他们组合的大致流程。部署的是一个web应用,从用户打开浏览器访问网页开始,到浏览到网页内容,这个过程就是各个技术实现功能的过程。</p> <h3>整体结构</h3> <ul> <li>用户浏览器(客户端)打开网页,向服务器发起请求;</li> <li>请求传给Nginx服务器,Nginx将请求发给uWSGI;</li> <li>uWSGI服务器发来的请求翻译为应用程序理解的形式,发给应用;</li> <li>Flask应用接收请求并处理,将响应结果发给uWSGI;</li> <li>uWSGI与Nginx服务器通信,将结果传给他;</li> <li>Nginx服务器收到响应结果,将其传给客户端;</li> <li>浏览器显示响应结果,并进行下一个请求。</li> </ul> <h2>安装Python环境</h2> <p>阿里云Ubuntu服务器自带的Python2.7和Python3.4,所以尽管我的应用是Python3程序,也不必重新装Python3。</p> <h3>更新apt-get</h3> <pre><code>sudo apt-get update </code></pre> <h3>获取应用源码</h3> <p>由于我的代码放在github仓库,直接通过git来安装。首先安装git:</p> <pre><code>sudo apt-get install git </code></pre> <p>安装完成后,在用户目录中新建project目录mkdir project,存放我们的应用程序。不知道是不是在用户目录可以输入指令pwd查看。我们转到project文件夹下,使用git克隆项目源码:</p> <pre><code>git clone https://github.com/Blackyukun/YuBlog.git </code></pre> <p>转到项目目录cd YuBlog</p> <h3>安装pip和virtualenv</h3> <pre><code>sudo apt-get install python-pip sudo apt-get install python-virtualenv </code></pre> <h3>创建虚拟环境</h3> <p>这里需要注意的是,如果直接virtualenv venv命令,创建的将会是Python2的虚拟环境。如果想要创建Python3的环境,需要指定Python3的目录:</p> <pre><code>virtualenv -p /usr/bin/python3 venv </code></pre> <p>如果成功,项目目录下会生成一个venv目录,那里就是我们的python3虚拟环境了。接下来激活虚拟环境:</p> <pre><code>source /home/xyy/py2env/bin/activate </code></pre> <p>退出虚拟环境命令是:deactivate</p> <h3>安装依赖包</h3> <p>如果项目实在虚拟环境中完成的,那么通常我们会使用pip freeze >requirements.txt命令列出项目所有依赖。然后当我们安装这些依赖的时候只需要使用命令:</p> <pre><code>pip install -i http://pypi.douban.com/simple/ -r requirements.txt </code></pre> <p>如果全部安装完成,那么我们的程序依赖环境全都准备好了。</p> <h2>安装Mysql数据库</h2> <p>我的程序是使用Mysql数据库做存储的,安装它也很简单,但是这里会有一个阿里云服务器的大坑。</p> <pre><code>sudo apt-get install mysql-server mysql-client sudo apt-get install libmysqlclient-dev </code></pre> <p>安装过程中会需要你输入用户以及密码,暂且就使用root和password吧。</p> <p>sudo netstat -tap | grep mysql命令检查Mysql是否安装成功,如果mysql的socket处于listen状态则表示安装成功。</p> <p>登录mysql数据库命令:mysql -u root -p这里的root就是之前安装是设置的用户名,接着输入密码password。在Linux上,我们需要修改mysql的默认编码为utf-8,以便正确地处理中文。</p> <p>这里需要编辑MySQL的配置文件,把数据库默认的编码全部改为UTF-8。MySQL的配置文件默认存放在/etc/my.cnf或者/etc/mysql/my.cnf:</p> <pre><code>vim /etc/mysql/my.cnf </code></pre> <p>linux使用的是vim编辑器,不了解vim的可以自行了解。我们按i进去插入模式,将下面的指令粘贴到对应位置:</p> <pre><code>[client] default-character-set = utf8 [mysqld] default-storage-engine = INNODB character-set-server = utf8 collation-server = utf8_general_ci </code></pre> <p>把队应指令放在对应地方就好了。配置完成后,在vim编辑器下,按ESC进入普通模式,键入:wq进行保存并退出。show variables like ‘%char%’;指令查看编码是否设置正确。如果看到utf8就表示正确。</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-1542984945cb80003ac1e31555562499.png" alt="未分类" width="509" height="210" class="aligncenter size-full wp-image-6607" /></p> <p>接着重启数据库:</p> <pre><code>service mysql restart </code></pre> <p>重新登录mysql -u root -p创建我们的数据库,使用:create database mydb;创建名为mydb的库名(注意后面封号)。</p> <h3>中文乱码</h3> <p>这里我们会遇到一个坑,就是在后面程序启动保存数据的时候会出现中文乱码,但是我们明明已经编辑过默认编码了呀。这里我发现是阿里云服务器本身没有安装中文包,我们需要进行安装。</p> <p>安装中文语言包</p> <pre><code>sudo apt-get -y install language-pack-zh-hans </code></pre> <p>修改语言环境设置</p> <pre><code>echo "LC_ALL=zh_CN.utf8" >> /etc/profile echo "export LC_ALL" >> /etc/profile </code></pre> <p>查看语言</p> <pre><code>source /etc/profile locale </code></pre> <p>看到zh_CN.UTF-8就成功了。接着需要重启服务器。</p> <h3>根本原因</h3> <p>虽然中文包安装成功了,但是这样就表示ok了吗?并没有,后来启动中网页中文显示依然乱码。我发现是保存数据库里的数据才会乱码,那么根本原因还是数据库编码问题。</p> <p>我们需要在创建数据库时要同时定义他的默认编码:</p> <p>删除数据库:mysql>delete database mydb;</p> <p>创建数据库:mysql>create database mydb default character set utf8;</p> <h2>安装Nginx服务器</h2> <p>Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件IMAP/POP3代理服务器。其特点是占有内存少,并发能力强。用于接收HTTP请求并返回响应。安装:</p> <pre><code>sudo apt-get install nginx </code></pre> <p>启动:sudo /etc/init.d/nginx start</p> <p>看到OK表示成功。接着需要配置Nginx。Nginx的配置文件在/etc/nginx/sites-available目录的default文件中,将其删除rm default。新的default创建并打开vim default,在里面写入:</p> <pre><code>server { listen 80; # 80端口需要打开 server_name X.X.X.X; #阿里云公网ip location / { include uwsgi_params; uwsgi_pass 127.0.0.1:5000; # 指向uwsgi 所应用的内部地址 uwsgi_param UWSGI_PYHOME /home/root/project/YuBlog/venv; # 虚拟环境目录 uwsgi_param UWSGI_CHDIR /home/root/project/YuBlog; # 应用根目录 uwsgi_param UWSGI_SCRIPT manage:app; # 启动程序 uwsgi_read_timeout 100; } } </code></pre> <p>重启Nginx:sudo service nginx restart</p> <p>看到OK表示成功。如果失败,可以输入指令sudo nginx -t查找错误,进行处理。</p> <h2>安装uWSGI</h2> <p>uWSGI虽然也可以起到Web服务器的作用,那么为什么有了uWSGI还需要Nginx呢。具体的优势大家自行了解。在Nginx+uWSGI的结构中,它充当中间件的程序,是Web的通信协议。</p> <p>安装:sudo pip install uwsgi</p> <p>注意实在虚拟环境。安装成功后需要配置。我们在项目的根目录下也就是/home/root/project/YuBlog下,创建配置文件config.ini,添加内容:</p> <pre><code>[uwsgi] master = true home = venv wsgi-file = manage.py callable = app socket = :5000 processes = 4 threads = 2 </code></pre> <p>配置完成后,就可以启动uWSGI了。但在这之前,我们先启动应用程序,并添加程序必须的环境变量。</p> <p>添加linux系统环境变量:export CONFIG=production</p> <p>…</p> <p>先创建迁移仓库:python manage.py db init</p> <p>创建迁移脚本,migrate子命令用来自动创建:python manage.py db migrate -m “v1.0”</p> <p>更新数据库操作:python manage.py db upgrade</p> <p>创建管理员信息:python manage.py addAdmin</p> <p>ctrl+c终止程序。</p> <p>启动uWSGI: uwsgi config.ini</p> <p>会看到很多信息,只要没有报错,就表明启动成功。</p> <h2>部署成功</h2> <p>如果Nginx和uWSGI全部启动成功,就说明部署已经成功了。打开外部浏览器,访问公网ip地址,就可以看到我们的程序已经跑起来了。</p> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2017/12/ubuntuuwsginginx-deployment-flask-application/" rel="bookmark"><time class="entry-date published updated" datetime="2017-12-20T22:11:10+08:00">2017年12月20日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a>、<a href="https://devops.webres.wang/tag/nginx-2/" rel="tag">nginx</a>、<a href="https://devops.webres.wang/tag/ubuntu/" rel="tag">ubuntu</a>、<a href="https://devops.webres.wang/tag/uwsgi/" rel="tag">uWSGI</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <article id="post-6419" class="post-6419 post type-post status-publish format-standard hentry category-undefine tag-flask tag-uwsgi"> <header class="entry-header"> <h2 class="entry-title"><a href="https://devops.webres.wang/2017/12/uwsgi-flask-deployment-website/" rel="bookmark">uwsgi+flask 部署网站</a></h2> </header><!-- .entry-header --> <div class="entry-content"> <p>摘要: 采用uwsgi作为web服务,将flask框架开发的web程序运行在centos云服务器上</p> <h2>概要</h2> <p>本文主要是叙述用uwsgi作为web服务运行flask开发的网站。本人查阅网上很多资料。跟着他们的步骤做,总是出现各种问题。折腾好久后,终于能正常运行了。故记录下来,供自己和他人查阅。</p> <h2>开发环境</h2> <p>centos7 64bit 云服务器,python 3.5 , uwsgi 2.0</p> <p>因为博主用flask开发的web使用的python3.5 ,所以,就在云服务器上安装了3.5.如果你的项目是其他版本python开发的,请安装对应的版本。这里不多加叙述安装问题。</p> <h2>安装uwsgi</h2> <p>因为我的python版本是3.5的。所以一下所有关于python的操作指令都是python3 和pip3.这个如果你的不是,请对应自己的来改。比如你的是python 2.7版本的,你的python指令的开头应该是python或者python2,安装包指令应该是pip2或者pip</p> <p>有很多方式提供安装,比如yum 安装,手动下载安装包,编译安装,已经pip安装。这里我们选择pip安装。这种方式感觉更靠谱。因为我用yum install uwsgi指令安装,最后uwsgi指定的python版本是我的旧版本2.7.而不是最新的3.5。所以卸载 了,又重新采用了pip安装</p> <p>安装uwsgi的指令如下:</p> <pre><code>pip3 install uwsgi </code></pre> <h2>建立软连接</h2> <p>因为我的python3.5是手动下载安装的。所以上一步安装uwsgi,安装成功后,只在我指定的安装目录下。而这个目录下,没有设置环境变量。所以我再shell上输入uwsgi,是不被识别的。为了解决这个问题,我需要建立一个软连接,然后将软连接文件放在环境变量中有的路径下。比如/usr/bin下。</p> <p>ln -s /usr/python3.5/bin/uwsgi /usr/bin/uwsgi<br /> *如果你安装完后,在shell上面,输入uwsgi已经能够被识别了,那么上面的指令你可以不用操作了。</p> <h2>安装一些依赖包</h2> <p>有些资料上面写着</p> <pre><code>yum install build-essential yum install python-dev </code></pre> <p>这两条指令,但是这两条指令,在centos上是不叫这个名字的。所以会发现安装不成功。</p> <p>以下是正确的安装指令:</p> <p>安装build-essential需要下面的指令:</p> <pre><code>yum groupinstall "Development Tools" yum install -y gcc yum install g++ yum kernel-devel </code></pre> <p>因为centos上面没有python-dev的包,而是要安装Python-devel</p> <pre><code>yum install Python-devel </code></pre> <p>flask程序包的目录结构</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-19277040055cb7ff4728bf41555562311.png" alt="未分类" width="356" height="178" class="aligncenter size-full wp-image-6415" /></p> <p>app是一个包,里面只有一个view.py文件和<strong>init</strong>文件.初始化文件是空的。下面贴出view.py文件的内容</p> <pre><code># encoding: utf-8 #!/usr/bin/ python3 ''' @author: rabbit @contact: 739462304@qq.com @time: 2017/11/27 14:09 @desc: ''' from flask import Flask app=Flask(__name__) @app.route("/") def hello(): return "Hello World" #if __name__ == "__main__": # app.run(host='0.0.0.0', port=5000) </code></pre> <p>manager.py的作用只是作为一个脚本启动入口,通过它启动flaskweb网站。比如你输入</p> <pre><code>python3 manage.py runserver --host 0.0.0.0 </code></pre> <p>manager.py 内容也贴出来给大家</p> <pre><code># encoding: utf-8 #!/usr/bin/env python ''' @author: rabbit @contact: 739462304@qq.com @time: 2017/11/27 14:15 @desc: ''' from flask_script import Manager, Shell from app.view import app manager=Manager(app) def make_shell_context(): return dict(app=app) manager.add_command("shell",Shell(make_context=make_shell_context)) @manager.command def deploy(): '''run deployment tasks''' pass if __name__=='__main__': manager.run() </code></pre> <h2>使用ini文件配置uwsgi,使网站运行起来</h2> <p>我们可以看到项目里面有个叫config.ini文件,这个是我后期加到项目里的。不是该网站程序所自带的。</p> <p>我们可以vim 去编辑这个config.ini并保存它。指令如下:</p> <p>先创建一个新的文件,名字叫config.ini</p> <pre><code>vim config.ini </code></pre> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-1564444805cb7ff477773d1555562311.png" alt="未分类" width="544" height="624" class="aligncenter size-full wp-image-6416" /></p> <p>也给出文件内容,方便大家复制粘贴</p> <pre><code>[uwsgi] #uwsgi启动时,所使用的地址和端口(这个是http协议的) http=0.0.0.0:5000 #uwsgi 启动时所使用的地址与端口(这个是socke协议) socket=0.0.0.0:8001 #指向网站目录 chdir=/root/deployTest #python 启动程序文件 wsgi-file=manage.py #python 程序内用以启动的application 变量名 callable=app #处理器数 processes=4 #线程数 threads=2 #状态检测地址 stats=127.0.0.1:9191 </code></pre> <p>对上面的内容我稍微解释一下。因为本人当初看别人写的这个地方的 配置,说的太模糊了。一直运行不起来,也不晓得是uwsgi没有安装好,还是这个配置文件的问题。搞的很头疼。简要说明如下:</p> <pre><code>chdir=/root/deloyTest </code></pre> <p>这个一定要指定到你的项目的目录中</p> <p>callable=app</p> <p>这个最难理解。因为当时项目最开始建立了一个叫app的文件夹,同时也建立了一个app.py的文件。app.py文件里面又有一个变量叫app。所以看到有的资料这么写,我很懵逼。最后我才把项目的app.py改成了view.py.经过本人测试。这个app的意思是对应view.py文件里面的flask变量名app.如果view.py里面的变量名改成了application。那么这个配置文件里面也要把app换成application.</p> <h2>启动uwsgi 挂起网站</h2> <p>一起准备妥当,只差最后临门一脚了</p> <p>指令如下:</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-12452035205cb7ff48094ea1555562312.png" alt="未分类" width="1043" height="616" class="aligncenter size-full wp-image-6417" /></p> <p>正常运行,界面如上。</p> <p>浏览器输入网址,应该就能得到我们网站的界面了。</p> <p>截图如下:</p> <p><img src="https://devops-cdn.webres.wang/wp-content/uploads/2019/04/linux-3194688755cb7ff48be2081555562312.png" alt="未分类" width="964" height="490" class="aligncenter size-full wp-image-6418" /></p> <p>最后祝你成功!</p> </div><!-- .entry-content --> <footer class="entry-footer"> <span class="posted-on"><span class="screen-reader-text">发布于 </span><a href="https://devops.webres.wang/2017/12/uwsgi-flask-deployment-website/" rel="bookmark"><time class="entry-date published updated" datetime="2017-12-05T22:50:14+08:00">2017年12月5日</time></a></span><span class="cat-links"><span class="screen-reader-text">分类 </span><a href="https://devops.webres.wang/category/undefine/" rel="category tag">未分类</a></span><span class="tags-links"><span class="screen-reader-text">标签 </span><a href="https://devops.webres.wang/tag/flask/" rel="tag">flask</a>、<a href="https://devops.webres.wang/tag/uwsgi/" rel="tag">uWSGI</a></span> </footer><!-- .entry-footer --> </article><!-- #post-## --> <nav class="navigation pagination" role="navigation"> <h2 class="screen-reader-text">文章导航</h2> <div class="nav-links"><a class="prev page-numbers" href="https://devops.webres.wang/tag/flask/page/3/">上一页</a> <a class='page-numbers' href='https://devops.webres.wang/tag/flask/'><span class="meta-nav screen-reader-text">页 </span>1</a> <span class="page-numbers dots">…</span> <a class='page-numbers' href='https://devops.webres.wang/tag/flask/page/3/'><span class="meta-nav screen-reader-text">页 </span>3</a> <span aria-current='page' class='page-numbers current'><span class="meta-nav screen-reader-text">页 </span>4</span> <a class='page-numbers' href='https://devops.webres.wang/tag/flask/page/5/'><span class="meta-nav screen-reader-text">页 </span>5</a> <span class="page-numbers dots">…</span> <a class='page-numbers' href='https://devops.webres.wang/tag/flask/page/7/'><span class="meta-nav screen-reader-text">页 </span>7</a> <a class="next page-numbers" href="https://devops.webres.wang/tag/flask/page/5/">下一页</a></div> </nav> </main><!-- .site-main --> </section><!-- .content-area --> </div><!-- .site-content --> <footer id="colophon" class="site-footer" role="contentinfo"> <div class="site-info"> <a href="https://cn.wordpress.org/" class="imprint"> 自豪地采用WordPress </a> </div><!-- .site-info --> </footer><!-- .site-footer --> </div><!-- .site --> <script type='text/javascript' src='https://devops.webres.wang/wp-content/themes/twentyfifteen/js/skip-link-focus-fix.js?ver=20141010'></script> <script type='text/javascript'> /* <![CDATA[ */ var screenReaderText = {"expand":"<span class=\"screen-reader-text\">\u5c55\u5f00\u5b50\u83dc\u5355<\/span>","collapse":"<span class=\"screen-reader-text\">\u6298\u53e0\u5b50\u83dc\u5355<\/span>"}; /* ]]> */ </script> <script type='text/javascript' src='https://devops.webres.wang/wp-content/themes/twentyfifteen/js/functions.js?ver=20150330'></script> <script type='text/javascript' src='https://devops.webres.wang/wp-includes/js/wp-embed.min.js?ver=5.1.19'></script> </body> </html>