python3 flask中SQLAlchemy创建表的简单介绍

在flask的SQLAlchemy中创建表,也是存在 ForeignKey(一对多) 与 ManyToMany(多对多) 的存在,以下是在models中单表、一对多、多对多的创建方式。

models.py代码如下:

import datetime
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Text,ForeignKey,DateTime,UniqueConstraint,Index
from sqlalchemy.orm import relationship

Base = declarative_base()

# 【单表创建的示例】
class Users(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True)
    name = Column(String(32),index=True)
    age = Column(Integer,default=18)
    email = Column(String(32),unique=True)  # unique = True 表示该字段中的值唯一
    ctime = Column(DateTime,default=datetime.datetime.now())
    extra = Column(Text,nullable=True)

    __table_args__ = (
        UniqueConstraint('id','name',name='unique_id_name'),  # 创建表中id和name字段的唯一索引,最后一个参数是索引名,注意前面要写name=
        Index('ix_name_email','name','email')   # 创建表中多个字段的普通索引
    )


# 【一对多示例】
# 表1
class Hobby(Base):
    __tablename__ = 'hobby'
    id = Column(Integer,primary_key=True)
    caption = Column(String(10),nullable=False,default='台球')

# 表2
class Person(Base):
    __tablename__ = 'person'
    nid = Column(Integer,primary_key=True)
    name = Column(String(32),index=True,nullable=True)
    # 创建一对多的关系
    hobby_id = Column(Integer,ForeignKey('hobby.id'))

    #   此字段不在表中生成,只是为了方便调用Hooby表;
    # 正向查找时可以直接用“hobby.”调用“Hobby”表中的字段。
    # 在hobby表中反向查找时可以用“pers”调用“Person”表中的字段。
    hobby = relationship('Hobby',backref='pers')


# 【多对多示例】
# 两表的关系表
class Server2Group(Base):
    __tablename__ = 'server2group'
    id = Column(Integer,primary_key=True,autoincrement=True)
    server_id = Column(Integer,ForeignKey('server.id'))
    group_id = Column(Integer,ForeignKey('group.id'))



# 表1
class Server(Base):
    __tablename__ = 'server'
    id = Column(Integer,primary_key=True,autoincrement=True)
    hostname = Column(String(15),nullable=True,unique=True)

    # 用于方便查找的标识
    groups = relationship('Group',secondary='server2group',backref='servers')

# 表2
class Group(Base):
    __tablename__ = 'group'
    id = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(32),nullable=True,unique=True)


def create_all():
    """
        根据类创建数据库表
        :return:
        """
    engine = create_engine(
        "mysql+pymysql://root:[email protected]:3306/test0621?charset=utf8",
        max_overflow=0,  # 超过连接池大小外最多创建的连接
        pool_size=5,  # 连接池大小
        pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
        pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
    )

    Base.metadata.create_all(engine)


def drop_db():
    """
    根据类删除数据库表
    :return:
    """
    engine = create_engine(
        "mysql+pymysql://root:[email protected]:3306/test0621?charset=utf8",
        max_overflow=0,  # 超过连接池大小外最多创建的连接
        pool_size=5,  # 连接池大小
        pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
        pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
    )

    Base.metadata.drop_all(engine)


if __name__ == '__main__':
    drop_db()
    create_all()

bootstrap flask登录页面编写实例

本文章来为各位介绍一个python的例子,这个就是bootstrap+flask写登录页面的例子,希望文章能够对各位有所帮助。
Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。在一般应用或个人开发中,可以很容易的写出应用。本篇就结合bootstrap,写一个简单的login界面。

一、效果图

无图无真像,先上效果图:

未分类

flask-login

未分类

二、目录结构

该代码写时采用动静分离的方法进行编写,目录树如下:

[root@jb51 login]# tree
.
├── run.py
├── static
│   └── css
│       ├── bootstrap.min.css
│       └── style.css
└── templates
    ├── index.html
    └── login.html

三、入口run文件

动态代码只有一个run.py文件,代码如下:

from flask import *
app = Flask(__name__,static_url_path='/static')
@app.route("/login",methods=['POST','GET'])
def login():
 error = None
 if request.method == 'POST':
 if request.form['username'] != 'admin' or request.form['password'] != 'admin123':
  error= "sorry"
 else:
  return redirect(url_for('index'))
 return render_template('login.html',error=error)
@app.route("/index")
def index():
 return render_template('index.html')
if __name__ == "__main__":
 app.run(
 host="0.0.0.0",
 port=80,
 debug=True)

实际应用中,根据需要,可以关闭debug模试。

四、静态模块

templates下有两个模块文件分别是login.html和index.html
login.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1.0" />
<title>Bootstrap响应式登录界面模板</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<div class="box">
<div class="login-box">
<div class="login-title text-center">
<h1><small>登录</small></h1>
</div>
<div class="login-content ">
<div class="form">
<form action="#" method="post">
<div class="form-group">
 <div class="col-xs-12 ">
  <div class="input-group">
   <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
   <input type="text" id="username" name="username" class="form-control" placeholder="用户名">
  </div>
 </div>
</div>
<div class="form-group">
 <div class="col-xs-12 ">
  <div class="input-group">
   <span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
   <input type="text" id="password" name="password" class="form-control" placeholder="密码">
  </div>
 </div>
</div>
<div class="form-group form-actions">
 <div class="col-xs-4 col-xs-offset-4 ">
  <button type="submit" class="btn btn-sm btn-info"><span class="glyphicon glyphicon-off"></span> 登录</button>
 </div>
</div>
<div class="form-group">
 <div class="col-xs-6 link">
  <p class="text-center remove-margin"><small>忘记密码?</small> <a href="javascript:void(0)" ><small>找回</small></a>
  </p>
 </div>
 <div class="col-xs-6 link">
  <p class="text-center remove-margin"><small>还没注册?</small> <a href="javascript:void(0)" ><small>注册</small></a>
  </p>
 </div>
</div>
</form>
</div>
</div>
</div>
</div>
<div ><p>来源:<a href="http://www.jb51.net/" target="_blank">运维之路</a></p></div>
</body>
</html>

index.html
index.html 模板中内容如下:

<h1>welcome to www.jb51.net/ <h1>

如果大家还想深入学习,可以点击这里进行学习,再为大家附两个精彩的专题:Bootstrap学习教程 Bootstrap实战教程
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持51dev开发社区。
以上就是bootstrap flask登录页面编写实例的全部内容,希望对大家的it技术学习有所帮助,希望大家多多支持技术开发者社区。

flask中jinjia2模板引擎使用详解1

在之前的文章中我们介绍过flask调用jinja2模板的基本使用,这次我们来说一下jinjia2模板的使用

Jinja2 在其是一个 Python 2.4 库之前,被设计 为是灵活、快速和安全的。

模板仅仅是文本文件。它可以生成任何基于文本的格式(HTML、XML、CSV、LaTex 等等)。 它并没有特定的扩展名, .html 或 .xml 都是可以的。

模板包含 变量 或 表达式 ,这两者在模板求值的时候会被替换为值。模板中 还有标签,控制模板的逻辑。模板语法的大量灵感来自于 Django 和 Python 。

下面是一个最小的模板,它阐明了一些基础。我们会在文档中后面的部分解释细节:

<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
</ul>
<h1>My Webpage</h1>
{{ a_variable }}
</body>
</html>

通过上面的模板我们能看到主要有两类标签组成 {{ }}和{% %}这两类标签分别用来包含变量和表达式

如何访问变量

如果传入的变量是对象,要访问对象中的属性,可以用下面两种方式:

{{obj.prop}}

{{obj[“prop”]}

两种方式都会去检查obj对象中有没有prop这个属性以及其中的其它变量,不同的是obj.prop先检查属性,obj[“prop”]先检查变量。

赋值

使用set关键字为变量设置值

<h1>Set为变量赋值</h1>
{% set name = 'Han Mei Mei' %}
{{ name }}

运行效果

未分类

变量过滤器

变量可以通过过滤器进行修改,变量和过滤器中间用|进行分隔,使用的基本格式是{{变量|过滤器1|过滤器2}},jinja2内置了很多过滤器,通过这些内置过滤器,可以进行变量的修改,内置过滤器可以参考 http://docs.jinkan.org/docs/jinja2/templates.html#builtin-filters ,比如我们要把变量转成大写,可以用upper过滤器

未分类

运行效果:

未分类

语句过滤器

上面说的是用过滤器过滤一个变量,下面来说一下如何在代码块中使用过滤器,上例子:

{% filter upper %}
This text becomes uppercase
{% endfilter %}

运行效果:

未分类

is关键字进行变量判断

is关键字用来在表达式中测试变量的值是否满足某条件。比如判断变量是否是数字,就可以用

<h1>name is number? {% if name is number %}true{% else %}false{%
endif %}</h1>

未分类

运行结果:

未分类

当我们修改一下代码,成下面的格式,限制输入为int:

@app.route("/tmpl/<int:name>")

def renderTmpl(name=1):

return render_template("hello.html",name=name)

再次运行:

未分类

Flask内置的is可用的函数参考 : http://docs.jinkan.org/docs/jinja2/templates.html#builtin-tests

欢迎关注“挨踢学霸”同名公众号, 回复jinjia2-1获取源码下载地址。

宝塔部署ubuntu+nginx+flask环境

准备

  1. 搭建好宝塔的vps,我用的是vultr
  2. flask应用在本地运行成功

在宝塔界面添加网站

  1. 设置好域名,数据库,php版本不管,或者在软件管理中卸载php
  2. 创建的web文件在/www/wwwroot/目录下
  3. 在本地的flask应用文件打包上传到刚刚创建的web文件中,解压

创建Python环境

$ sudo apt install python3-venv
$ python3 -m venv venv
# 如果venv/bin/下没有activate,就表示没有成功,解决如下
$ export LC_ALL="en_US.UTF-8"
$ export LC_CTYPE="en_US.UTF-8"
$ sudo dpkg-reconfigure locales
# 进入虚拟环境
$ source venv/bin/activate
# 安装uWSGI和requirements
$ pip install uwsgi
$ pip install -r requirements.txt
# 退出虚拟环境
$ deactivate

配置uwsgi

在应用根目录创建config.ini文件,内容如下

[uwsgi]
# uwsgi 启动时所使用的地址与端口
socket = 127.0.0.1:8386
# 指向网站目录
chdir = /www/wwwroot/www.itswcg.site
# python 启动程序文件
wsgi-file = main.py
# python 程序内用以启动的 application 变量名
callable = app 
# 处理器数
processes = 4
# 线程数
threads = 2
#状态检测地址
stats = 127.0.0.1:9191

配置nginx

在宝塔面板中,管理网站,设置,配置文件修改如下,
或者在/www/server/panel/vhost/nginx/.conf下修改

server {
  listen  80; 如有多个web应用,都是80端口监听
  server_name resume.itswcg.com; #地址
  location / {
    include      uwsgi_params;
    uwsgi_pass   127.0.0.1:8386;  # 指向uwsgi 所应用的内部地址,所有请求将转发给uwsgi 处理
    uwsgi_param UWSGI_PYHOME /www/wwwroot/www.itswcg.site/venv; # 指向虚拟环境目录
    uwsgi_param UWSGI_CHDIR  /www/wwwroot/www.itswcg.site; # 指向网站根目录
    uwsgi_param UWSGI_SCRIPT main:app; # 指定启动程序
  }
}

重启

$ sudo service nginx restart

这时候运行如下,不出错,输入网址就成功了

$ uwsgi config.ini

配置supervisor

supervisor能同时启动多个应用,能自动重启应用,保证可用性。
安装

$ sudo apt-get install supervisor

在/etc/supervisor/conf.d下添加.conf文件(resume.conf),内容如下

[program:resume] #resume是<name>
##注意项目目录和uwsgi的配置文件地址
command=/www/wwwroot/www.itswcg.site/venv/bin/uwsgi /www/wwwroot/www.itswcg.site/config.ini
directory=/www/wwwroot/www.itswcg.site
autostart=true
autorestart=true
user = root
##log文件的位置
stdout_logfile=/www/wwwroot/www.itswcg.site/logs/uwsgi_supervisor.log

启动

supervisord -c /etc/supervisor/supervisord.conf

客户端管理

$ supervisorctl

这样你就不用每次重启时都运行$ uwsgi config.ini,supervisor帮你自动重启

还有别忘了在宝塔面板安全中,放行端口

未分类

使用Python和Flask编写Prometheus监控

Installation

pip install flask
pip install prometheus_client

Metrics

Prometheus提供4种类型Metrics:Counter, Gauge, Summary和Histogram

Counter

Counter可以增长,并且在程序重启的时候会被重设为0,常被用于任务个数,总处理时间,错误个数等只增不减的指标。

import prometheus_client
from prometheus_client import Counter
from prometheus_client.core import CollectorRegistry
from flask import Response, Flask
app = Flask(__name__)
requests_total = Counter("request_count", "Total request cout of the host")
@app.route("/metrics")
def requests_count():
    requests_total.inc()
    # requests_total.inc(2)
    return Response(prometheus_client.generate_latest(requests_total),
                    mimetype="text/plain")
@app.route('/')
def index():
    requests_total.inc()
    return "Hello World"
if __name__ == "__main__":
    app.run(host="0.0.0.0")

运行改脚本,访问youhost:5000/metrics

# HELP request_count Total request cout of the host
# TYPE request_count counter
request_count 3.0

Gauge

Gauge与Counter类似,唯一不同的是Gauge数值可以减少,常被用于温度、利用率等指标。

import random
import prometheus_client
from prometheus_client import Gauge
from flask import Response, Flask
app = Flask(__name__)
random_value = Gauge("random_value", "Random value of the request")
@app.route("/metrics")
def r_value():
    random_value.set(random.randint(0, 10))
    return Response(prometheus_client.generate_latest(random_value),
                    mimetype="text/plain")
if __name__ == "__main__":
    app.run(host="0.0.0.0")

运行改脚本,访问youhost:5000/metrics

# HELP random_value Random value of the request
# TYPE random_value gauge
random_value 3.0

Summary/Histogram

Summary/Histogram概念比较复杂,一般exporter很难用到,暂且不说。

PLUS

LABELS

使用labels来区分metric的特征

from prometheus_client import Counter
c = Counter('requests_total', 'HTTP requests total', ['method', 'clientip'])
c.labels('get', '127.0.0.1').inc()
c.labels('post', '192.168.0.1').inc(3)
c.labels(method="get", clientip="192.168.0.1").inc()

REGISTRY

from prometheus_client import Counter, Gauge
from prometheus_client.core import CollectorRegistry
REGISTRY = CollectorRegistry(auto_describe=False)
requests_total = Counter("request_count", "Total request cout of the host", registry=REGISTRY)
random_value = Gauge("random_value", "Random value of the request", registry=REGISTRY)

FLASK实现机器负载监控

  • 系统环境:centos7.4

  • 主机IP:192.168.134.149

Flask代码段:

未分类

moitor.html页面:

<!DOCTYPE html>

<html>

<head>

<meta charset=”utf-8″>

<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>

<title>运维管理系统</title>

<link rel=”shortcut icon” href=”favicon.ico”>

<link href=”{{url_for(‘static’,filename=’css/plugins/morris/morris-0.4.3.min.css’)}}” rel=”stylesheet”>

</head>

<body>

<script src=”{{ url_for(‘static’,filename=’js/jquery.min.js’) }}”></script>

<script src=”{{url_for(‘static’,filename=’js/plugins/morris/raphael-2.1.0.min.js’)}}”></script>

<script src=”{{url_for(‘static’,filename=’js/plugins/morris/morris.js’)}}”></script>

<script type=”text/javascript”>

$(function () {

function monitor() {

$(‘#myfirstchart’).html(“”);

var SCRIPT_ROOT = {{ request.script_root|tojson|safe }}

$.post(SCRIPT_ROOT + ‘/monitor’,function(data){

Morris.Line({

element: ‘myfirstchart’,

data: data ,

xkey: ‘timenow’,

ykeys: [‘a’,’b’,’c’],

labels: [‘1分钟’, ‘5分钟’,’15分钟’]

});

})

};

setInterval(monitor,3000)

})

</script>

<div id=”myfirstchart” style=”height: 250px;”></div>

</body>

</html>

效果图:

未分类

使用python3和flask构建RESTful API(接口测试服务)

引言

构建RESTful API貌似是开发的工作,和测试有和关系?

其实测试开发需要构建RESTful API的场景很多。比如测试Android应用,一般的接口测试只考虑了服务器端,至于客户端在网络异常或者服务端异常时如何反应,多数天朝的测试人员是没有考虑到的。客户端在对这些异常处理不够充分的时候,会出现崩溃等各种莫名其妙的问题。

为此一些走在前沿的测试人员会自己写一些RESTful API, 把服务端的域名劫持到自己的API,故意返回各种异常,看客户端的稳定性。

另外测试开发的测试工具需要和其他系统对接等场景也经常需要API。

术语

REST: REpresentational State Transfer

目标

  • GET – /api/Category – Retrieve all categories

  • POST – /api/Category – Add a new category

  • PUT – /api/Category – Update a category

  • DELETE – /api/Category – Delete a category

  • GET – /api/Comment – Retrieve all the stored comments

  • POST – /api/Comment – Add new comment

要求

  • python3.*
  • PostgreSQL

工程目录

project/
├── app.py
├── config.py
├── migrate.py
├── Model.py
├── requirements.txt
├── resources
│   └── Hello.py
│   └── Comment.py
│   └── Category.py
└── run.py

requirements.txt的内容如下:

flask
flask_restful
flask_script
flask_migrate
marshmallow
flask_sqlalchemy
flask_marshmallow
marshmallow-sqlalchemy
psycopg2
  • flask – Python的微框架

  • flask_restful – 这是Flask的扩展,可快速构建REST API。

  • flask_script – 提供了在Flask中编写外部脚本的支持。

  • flask_migrate – 使用Alembic的Flask应用进行SQLAlchemy数据库迁移。

  • marshmallow – ORM/ODM/框架无关的库,用于复杂数据类型(如对象)和Python数据类型转换。

  • flask_sqlalchemy – Flask扩展,增加了对SQLAlchemy的支持。

  • flask_marshmallow – 这是Flask和marshmallow的中间层。

  • marshmallow-sqlalchemy – 这是sqlalchemy和marshmallow的中间层。

  • psycopg – Python的PostgreSQL API。

安装依赖

# pip3 install -r requirements.txt

安装配置PostgreSQL

这里以 Ubuntu 16.04为例:

# sudo apt-get update && sudo apt-get upgrade
# apt-get install postgresql postgresql-contrib
# su - postgres
$ createdb api
$ createuser andrew --pwprompt #创建用户
$ psql -d api -c "ALTER USER andrew WITH PASSWORD 'api';"

参考资料:

https://linode.com/docs/databases/postgresql/how-to-install-postgresql-on-ubuntu-16-04/

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-14-04

配置

# -*- coding: utf-8 -*-
# Author:    xurongzhong#126.com wechat:pythontesting qq:37391319
# CreateDate: 2018-1-10

from flask import Blueprint
from flask_restful import Api
from resources.Hello import Hello
from resources.Category import CategoryResource
from resources.Comment import CommentResource


api_bp = Blueprint('api', __name__)
api = Api(api_bp)

# Routes
api.add_resource(Hello, '/Hello')
api.add_resource(CategoryResource, '/Category')
api.add_resource(CommentResource, '/Comment')

快速入门

app.py

from flask import Blueprint
from flask_restful import Api
from resources.Hello import Hello

api_bp = Blueprint('api', __name__)
api = Api(api_bp)

# Route
api.add_resource(Hello, '/Hello')

resource/Hello.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author:    xurongzhong#126.com wechat:pythontesting qq:37391319
# CreateDate: 2018-1-10

from flask_restful import Resource


class Hello(Resource):
    def get(self):
        return {"message": "Hello, World!"}

    def post(self):
        return {"message": "Hello, World!"}

run.py

from flask import Flask


def create_app(config_filename):
    app = Flask(__name__)
    app.config.from_object(config_filename)

    from app import api_bp
    app.register_blueprint(api_bp, url_prefix='/api')

    return app


if __name__ == "__main__":
    app = create_app("config")
    app.run(debug=True)

启动服务

$ python3 run.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 136-695-873

用浏览器访问: http://127.0.0.1:5000/api/Hello

{
    "hello": "world"
}

接入数据库

from flask import Flask
from marshmallow import Schema, fields, pre_load, validate
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy


ma = Marshmallow()
db = SQLAlchemy()


class Comment(db.Model):
    __tablename__ = 'comments'
    id = db.Column(db.Integer, primary_key=True)
    comment = db.Column(db.String(250), nullable=False)
    creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id', ondelete='CASCADE'), nullable=False)
    category = db.relationship('Category', backref=db.backref('comments', lazy='dynamic' ))

    def __init__(self, comment, category_id):
        self.comment = comment
        self.category_id = category_id


class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), unique=True, nullable=False)

    def __init__(self, name):
        self.name = name


class CategorySchema(ma.Schema):
    id = fields.Integer()
    name = fields.String(required=True)


class CommentSchema(ma.Schema):
    id = fields.Integer(dump_only=True)
    category_id = fields.Integer(required=True)
    comment = fields.String(required=True, validate=validate.Length(1))
    creation_date = fields.DateTime()

migrate.py

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from Model import db
from run import create_app

app = create_app('config')

migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)


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

数据迁移

$ python3 migrate.py db init
$ python3 migrate.py db migrate
$ python migrate.py db upgrade

修改Category.py 和Comment.py, 完整代码

测试

可以使用curl,比如:

curl http://127.0.0.1:5000/api/Category --data '{"name":"test5","id":5}' -H "Content-Type: application/json"

也可以在chrome中使用postman:

未分类

未分类

未分类

未分类

[Flask教程] 9.注册和登录功能实现(2)—— 注册与登录的错误提示

在注册和登录功能实现(1)中,我们已经获取到了页面POST过来的登录或者注册数据,接下来我们需要与数据库中的数据做验证,验证通过才能登录或者注册。我们平时在登录网站时,如果输入的用户名或者密码错误,有的网站是在登录框附近提示错误,也有的是跳转到一个页面提示出错,并经过几秒倒计时再返回原来的页面。
我们在后续做搜索功能的时候,用页面跳转来处理未找到结果的情形,这里就通过使用Flask的flash功能,直接在当前页面显示错误提示。简单来说,步骤就是在视图函数中flash一个字符串,在html模板中使用get_flashed_messages()去获取这个字符串,并显示在网页中。
首先,我们先新建一个exts.py,用于存放一些功能性的函数,在其中写一个去验证登录和注册信息的函数,如下:

from models import Users

def validate(username, password1, password2=None):
    user = Users.query.filter(Users.username == username).first()
    if password2:
        if user:
            return '用户名已经存在'
        else:
            if len(username) < 4:
                return '用户名长度至少4个字符'
            elif password1 != password2:
                return '两次密码不一致'
            elif len(password1) < 6:
                return '密码长度至少6个字符'
            else:
                return '注册成功'
    else:
        if user:
            if user.password == password1:
                return '登录成功'
            else:
                return '密码错误'
        else:
            return '用户名不存在'

要使用flash功能,还得设置一个名为SECRET_KEY的参数,用于加密数据,我们在config.py中写进去,随便取个值SECRET_KEY = “THIS-A-SECRET-KEY”。然后在HarpQA.py中,从flask中导入flash,从exts.py中导入validate,修改register视图函数,如下:

from flask import Flask, render_template, request, flash
from models import db
from exts import validate
import config

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

...

@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        username = request.form.get('username')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')
        message = validate(username, password1, password2)
        flash(message)
        return render_template('register.html')

在视图函数中flash了message,接下来我们需要在html中显示它,我们再去修改base.html(这样对register.html和login.html都能起作用),在body区域尾部增加如下代码(直接从Flask官方文档复制修改的):

...
<nav><!-- 导航条内容 -->
...
</nav>
<div class="body-container">
    {% block body_part %}
    {% endblock %}
</div>
<div class="flash-message">
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul>
            {% for message in messages %}
                <li>{{ message }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
</div>
</body>

然后运行程序,访问注册页,随便输入几个试试,发现已经能用了:

未分类

只是不太美观,我们再用css调整一下,借助Boostrap中的警告框样式,最终结果如下:

未分类

登录也是同理,就不演示啦。

2017年1月17日补充内容:
当我们登录或注册出现问题,出现的警告框是如上图红色的,显然我们不希望登录或者注册成功的时候也是红色的,我们将提示成功的颜色设置为蓝色,然后传入一个参数给模板,告诉模板现在是成功还是失败的情况,然后在html中增加if,现在的html代码如下:

<div class="flash-message">
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul>
            {% for message in messages %}
                {% if status == 'OK' %}
                    <li><div class="alert alert-info" role="alert">{{ message }}</div></li>
                {% else %}
                    <li><div class="alert alert-danger" role="alert">{{ message }}</div></li>
                {% endif %}
            {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
</div>

那么status这个参数是怎么传递给模板呢,我们之前提到在render_templates函数中传入,现在我们使用@app.context_processor这个装饰器,它其实是上下文管理器,其装饰的函数返回的内容对所有html模板都起作用,用法如下:

@app.context_processor
def my_context_processor():
    status = session.get('status', '')
    return {'status': status}

将其放在HarpQA.py中,它返回一个字典,在任意html中使用{{ key }},就能得到这个字典key对应的value。那么session是什么?在现在这个case中,我们简单理解其为一个保存数据的容器,在登录和注册的视图函数中,验证完账号密码之后,将验证结果的信息存入session,例如注册函数修改如下:

@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        username = request.form.get('username')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')
        message = validate(username, password1, password2)
        flash(message)
        if message == '注册成功':
            session['status'] = 'OK'
            return redirect(url_for('login'))
        else:
            session['status'] = 'BAD'
            return render_template('register.html')

如果注册成功,就向session中写入status,值为’OK’,反之则为’BAD’。这样html模板就能根据status显示不同的颜色了。flask中还有个g对象也可用于保存和共享数据,但g对象是基于每一个请求的,不能跨请求使用。我们注册成功之后,要跳转到登录页,此时g就无法使用了,而session是基于这一次http连接的,不同请求都能使用。

未分类

实际上更简单的方法是,我们直接对传入的{{ message }}进行判断,如果带有’成功’字符串,就显示蓝色,否则就显示红色。上文主要是为了说明@app.context_processor这个装饰器,以及session和g对象的区别。

[Flask教程] 8.注册和登录功能实现(1)—— 页面设计和获取POST数据

在导航条中,右侧有登录和注册两个链接,储存用户数据的Users模型也有了,现在我们来实现登录和注册的功能。
先来看注册功能的实现,新建一个视图函数,如下:

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

在导航条模板base.html中,为注册添加链接,使用url_for函数将其href属性值修改为register.html,如下:

<li><a href="{{ url_for('register') }}">注册</a></li>

接下来我们要去制作register.html了,这部分是基础的html/css知识,控件也是用的Bootstrap框架,最终注册页http://127.0.0.1:5000/register/的效果图如下(结尾贴出代码):

未分类

register.html中的表单控件,我们设置了其方法为POST,注册按钮的type为submit,这样点击提交按钮的时候,浏览器就会以POST方法去请求当前网址,但我们的视图函数默认只能接受GET方法,因此我们要为其添加POST方法,并使用flask中的request对象获取表单提交的数据,最终视图函数代码如下:

@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        username = request.form.get('username')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')
        print(username, password1, password2)
        return ' '

request.form.get方法需要传入表单控件的name属性的值,这样就能获取到对应的填入到表单中的值了,为了演示,我们在结尾将其打印了出来,结果显示无误,说明后端已经获取到了POST提交的数据。
登陆页面也是一样的原理,拿register.html简单修改成login.html,模仿register再增加一个login视图函数,然后访问http://127.0.0.1:5000/login/,效果如下:

未分类

结尾贴上register.html的内容:

{% extends 'base.html' %}

{% block static_files %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/register_login.css.css') }}"/>
{% endblock %}

{% block page_name %}注册{% endblock %}

{% block body_part %}
<h3>注册</h3>
<form class="form" action="" method="POST">
    <div class="input-group input-group-lg">
      <span class="input-group-addon" id="sizing-addon1">用户</span>
      <input type="text" class="form-control" placeholder="请输入用户名" aria-describedby="sizing-addon1" name="username">
    </div>
    <div class="input-group input-group-lg">
      <span class="input-group-addon" id="sizing-addon1">密码</span>
      <input type="password" class="form-control" placeholder="请输入密码" aria-describedby="sizing-addon1" name="password1">
    </div>
    <div class="input-group input-group-lg">
      <span class="input-group-addon" id="sizing-addon1">密码</span>
      <input type="password" class="form-control" placeholder="请再次确认密码" aria-describedby="sizing-addon1" name="password2">
    </div>
    <div class="register-button">
        <button type="submit" class="btn btn-primary btn-lg btn-block">注册</button>
    </div>
</form>
{% endblock %}

代码中引入的register_login.css是register.html和login.html通用的,其内容如下:

h3{
    text-align: center;
    padding-top: 10px;
}

.form{
    width: 400px;
    margin: 0 auto;
}

form.form > div{
    padding: 8px;
}

此外还对导航条模板做了简单的调整,第一是增加了一个block,给每个继承它的html一个放自定义css文件的地方,第二是在body中增加了一个div块,即register.html和login.html中间白色的内容区域,最终base.html代码如下:

<!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') }}">
    {% block static_files %}{% endblock %}
    <title>{% block page_name %}{% endblock %}-HarpQA</title>
</head>
<body>
    <nav><!-- 导航条内容 -->
        ...省略...
        <li><a href="{{ url_for('login') }}">登录</a></li>
        <li><a href="{{ url_for('register') }}">注册</a></li>
        ...省略...
    </nav>
    <div class="body-container">
        {% block body_part %}
        {% endblock %}
    </div>
</body>
</html>

base.css 也添加了 <div class="body-container"> 的样式:

.body-container{
    width: 600px;
    background: white;
    margin: 0 auto;
    border-radius: 5px;
}

[Flask教程] 7.ORM与SQLAlchemy (3) – flask-migrate数据库迁移

在上一遍文章中,我们增加了两个模型Questions和Comments,并为Users增加了avatar_path这个字段,然后通过这段代码更新到数据库:

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

因为当使用过db.create_all()之后,再次直接使用db.create_all(),对模型的修改并不会更新到数据库,我们要使用db.drop_all()先把数据库中所有的表先删除掉,然后再db.create_all()一次。听上去是不是很麻烦?更糟糕的是,原先数据库的的数据也就没有了。所以我们不用这种简单粗暴的方式去更新数据库结构,而是借助flask-migrate这个专门用于迁移数据库的工具,它可以在保留数据库原始数据的情况下,完成模型的更新。此外,我们还将结合flask-script一起使用,简单来说flask-script让我们可以使用命令行去完成数据库迁移的操作。

在项目主文件夹下新建一个manage.py,代码如下:

from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from HarpQA import app, db
from models import Users, Questions, Comments

manager = Manager(app)

migrate = Migrate(app, db)

manager.add_command('db', MigrateCommand)


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

首先导入相关的类,注意模型要全部导入过来,即使代码中并没有显式地使用它们。然后传入app或db来构建Manager和Migrate两个类的实例,最后将MigrateCommand的命令加入到manager中。

此时我们假设要更新模型的结构,在models.py的User模型结尾添加一行代码test = db.Column(db.Integer),然后点击PyCharm下方的Terminal,自动进入到了虚拟环境的命令行中,输入python manage.py db init来初始化,这一步主要是建立数据库迁移相关的文件和文件夹,只是在第一次需要使用。接着依次使用python manage.py db migrate和python manage.py db upgrade,待运行完成,查看users_infor表的结构,结果如下:

未分类

可以看到test字段已经添加到表中了。