Python_day25--Flask插件:Flask-SQLAlchemy、Flask-Bootstrap

一、Flask-SQLAlchemy

1、ORM    框架

       Web开发中,一个重要的组成部分便是数据库了。Web程序中最常用的莫过于关系型数据库了,也称SQL数据库。另外,文档数据库(如    mongodb)、键值对数据库(如redis)近几年也逐渐在 web开发中流行起来,我们习惯把这两种数据库称为NoSQL数据库。
大多数的关系型数据库引擎(比如MySQL、Postgres和SQLite)都有对应的Python包。在这里,我们不直接使用这些数据库引擎提供的  Python包,而是使用对象关系映射(Object-Relational Mapper,ORM)框架,它将低层的数据库操作指令抽象成高层的面向对象操作。也就是说,如果我们直接使用数据库引擎,我们就要写SQL操作语句,但是,如果我们使用了ORM框架,我们对诸如表、文档此类的数据库实体就可以简化成对Python对象的操作。Python中最广泛使用的ORM框架是SQLAlchemy,它是一个很强大的关系型数据库框架,不仅支持高层的ORM,也支持使用低层的SQL操作,另外,它也支持多种数据库引擎,如MySQL、Postgres和SQLite等。

2、Flask-SQLAlchemy

在Flask中,为了简化配置和操作,我们使用的ORM框架是Flask-SQLAlchemy,这个Flask扩展封装了SQLAlchemy框架。在Flask-SQLAlchemy中,数据库使用URL指定,下表列出了常见的数据库引擎和对应的    URL。

3、安装

首先,我们使用pip安装Flask-SQLAlchemy,安装在你所需要的环境中,本作者使用的是虚拟环境,虚拟环境能比较好的解决依赖问题,

pip install flask-sqlalchemy

 4、应用

1)链接数据库

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://root:[email protected]/TodoProject"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# app.config['SECRET_KEY'] = 'westos'
app.config['SECRET_KEY'] =  os.urandom(24)
Bootstrap(app)

# 实例化db对象
db = SQLAlchemy(app)

这里有几点需要注意的是:
1、app应用配置项SQLALCHEMY_DATABASE_URI指定了SQLAlchemy所要操作的数据库,这里我们使用的是mysql数据库,引号里是你需要链接的数据库://用户名和密码@本机localhost或远程链接某个数据库,/后面是你要链接数据库的名称;这里特别说一点就是数据库的编码格式问题,当你在创建数据库时可以使用下面这个命令来解决你的编码问题;

create database 数据库名称 default charset utf8;

'SQLALCHEMY_DATABASE_URI'    =====》链接 数据库;

'SQLALCHEMY_TRACK_MODIFICATIONS' ======》Ture

'SECRET_KEY' =====》加密

2、db    对象是    SQLAlchemy    类的实例,表示程序使用的数据库。

3、我们定义的User模型必须继承自db.Model,这里的模型其实就对应着数据库中的表。其中,类变量 __tablename__ 定义了在数据库中使用的表名,如果该变量没有被定义,Flask-SQLAlchemy会使用一个默认名字。

2)建表

在这里我们的建表有了突破性的进展,前面我们一直说mysql是一个关系型数据库,但是没有体现出“关系”二字,在这里我们就要建立有关系的数据表;

我们设想这样的一个场景:我们在公司的内部系统上每一天都会有任务发布,有任务本身和它相关的执行部门,他们两个关系在任务产生的时候就被绑定在了一起,这样也就完成了我们所说的关系,那么我们在数据库中怎样建立数据表之间的关系呢?

外键约束是在两张表中建立的。两张表存在父子关系,即:子表中的某个字段的取值有父表决定的。
结论:在一对多关系中额,外键建立在多的一方

例如:简单的例子在很多的电影网站上都有会员,超级会员,普通会员这三种会员身份,我们需要对普通用户的身份进行关联

# 创建user表
class Users(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    # unique: 指定该列信息是唯一的;
    name = db.Column(db.String(50), unique=True)
    passwd = db.Column(db.String(50))
    # default是默认值l
    add_time = db.Column(db.DateTime, default=datetime.now())
    role_id = db.Column(db.Integer, db.ForeignKey('role.id'))  #和哪张表的哪个表头进行关联





# 创建用户角色(超级会员, 会员, 普通会员)
class Role(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    name = db.Column(db.String(50), unique=True)
    # 'Users代表的是Role表关联的数据库表'
    # backref=“role”: 反向引用;让Users中有role属性, user.role返回的是role对象。
    users = db.relationship('Users', backref='role')   #上表和这里的进行相关联

    #
    # def __repr__(self):
    #     """如果查询数据表内容, 需要特色化设置时, 使用__repr__"""
    #     return  "" %(self.name)
    


# 1. 创建表
# db.create_all()

 下面的是我们更为复杂的例子:

class Todo(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    # unique: 指定该列信息是唯一的;
    name = db.Column(db.String(50))
    # default是默认值l
    add_time = db.Column(db.DateTime, default=datetime.now())
    status = db.Column(db.Boolean, default=False)
    department_id = db.Column(db.Integer, db.ForeignKey('department.id'))


class Department(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    name = db.Column(db.String(50), unique=True)
    todos = db.relationship('Todo', backref='department')
    users = db.relationship('User', backref='department')


class User(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    # unique: 指定该列信息是唯一的;
    name = db.Column(db.String(50), unique=True)
    # 此处为了用户帐号的安全性, 必须对密码进行加密;
    pwd = db.Column(db.String(100))
    email = db.Column(db.String(20), unique=True)
    phone = db.Column(db.String(20), unique=True)
    info = db.Column(db.Text)  # 个性简介
    addtime = db.Column(db.DateTime, default=datetime.now())
    department_id = db.Column(db.Integer, db.ForeignKey('department.id'))
    userlogs = db.relationship('UserLog', backref="user")

    def check_pwd(self, pwd):
        return check_password_hash(self.pwd, 'westos1')


# 用户登录日志
class UserLog(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    ip = db.Column(db.String(100))  # 登录的IP
    addtime = db.Column(db.DateTime, default=datetime.now())
    area = db.Column(db.String(100))  # 登录的城市

Python_day25--Flask插件:Flask-SQLAlchemy、Flask-Bootstrap_第1张图片

3)给表中添加信息

1、建表

    # 1). 创建表
    # db.create_all()

2、给表内添加信息

 2). 初始化数据
    parts = ['开发部', '运维部', '人事部']
    partObj = [Department(name=part) for part in parts]
    db.session.add_all(partObj)
    db.session.commit()



    todos = ['打扫卫生', '开发微电影管理系统', '招聘人才']
    todoObj = [Todo(name=todo, department_id=random.choice([1,2,3]) ) for todo in todos]
    db.session.add_all(todoObj)
    db.session.commit()

3、添加用户的测试数据

u1 = User(name="westos1", pwd=generate_password_hash('westos'), department_id=1)
    u2 = User(name="westos2", pwd=generate_password_hash('westos'), department_id=2)
    u3 = User(name="westos3", pwd=generate_password_hash('westos'), department_id=1)

    db.session.add_all([u1, u2, u3])
    db.session.commit()

注意:这里我们用到了哈希加密

什么是哈希加密:HASH主要用于信息安全领域中加密算法,他把一些不同长度的信息转化成杂乱的128位的编码里,叫做HASH值. 也可以说,hash就是找到一种数据内容和数据存放地址之间的映射关系

这里我们用到的加密模块为:

from werkzeug.security import generate_password_hash

Python_day25--Flask插件:Flask-SQLAlchemy、Flask-Bootstrap_第2张图片

相对应的也就有解密模块:

from werkzeug.security import check_password_hash

Python_day25--Flask插件:Flask-SQLAlchemy、Flask-Bootstrap_第3张图片

解密的时候我们需要两个参数:分别是哈希加密后的一串代码,再其次是我们的明文密码,返回值为Ture 或 False

4)查询数据信息

# 查询所有的角色;
print(Role.query.all())

1、 如何显示会员类型与该会员类型包含的所有用户;

roles为对象。对象对应的属性我们都可以看到

roles = Role.query.all()
for role in roles:
    print(role.id, role.name,role.users)

2、显示所有的用户以及用户类别

users = Users.query.all()
for user in users:
    print(user.id, user.name, user.passwd, user.role)

3、根据条件查找符和条件的数据和更新数据;(filter_by(常用), filter)

# 1). 找到所有超级会员的用户;
users = Users.query.filter_by(role_id=1).all()


# 2). 更新所有超级会员的密码为0000000;
for user in users:
    # print(user.passwd)
    user.passwd = "000000"
    print("更新用户%s的密码成功!" %(user.name))
    db.session.add(user)
    db.session.commit()

4、其他的查询信息显示限制条件

users = Users.query.filter_by(role_id=1).limit(10).all()
print(users, len(users))

5、排序: 根据用户创建的时间进行排序(默认情况正序, 如果要逆序desc())

users = Users.query.order_by(desc(Users.add_time)).all()
for u in users[:10]:
    print(u.add_time)

6、limit可以限制查询数据的数量;

users =Users.query.order_by(desc(Users.add_time)).limit(5).all()
print(users)

7、offset: 偏移量, 可以设置查询偏移量,也就是数据的起始位置, 一般与limit结合使用;

print(Users.query.order_by(desc(Users.add_time)).limit(5).offset(2).all())

8、slice: 切片

print(Users.query.order_by(desc(Users.add_time)).slice(1,5).all())

9、分页:第一个参数为页码,第二个参数为每一页显示的内容

page_u = Users.query.paginate(1, 3)
print(page_u.items)
print(dir(page_u))

page_u = Users.query.paginate(2, 3)
print(page_u.items)

10、用户数据更新;

u = Users.query.filter_by(id=1).first()
print(u.id, u.name, u.add_time)

u.name = "西部开源1"
db.session.add(u)
db.session.commit()


u = Users.query.filter_by(id=1).first()
print(u.id, u.name, u.add_time)

Python_day25--Flask插件:Flask-SQLAlchemy、Flask-Bootstrap_第4张图片 

11、用户信息删除;

u = Users.query.filter_by(id=1).first()
db.session.delete(u)
db.session.commit()


u = Users.query.filter_by(id=1).first()
print(u.id, u.name, u.add_time)

 

二、Flask-Bootstrap

Bootstrap是Twitter开源的一个CSS/HTML框架,它让Web开发变得更加迅速,简单。要想在我们的Flask应用中使用Boostrap,有两种方案可供选择:
第1种,在我们的Jinja2模板中直接引入Bootstrap层叠样式表(CSS)和JavaScript文件,比如bootstrap.min.css,bootstrap.min.js;
第2种,也是更简单的方法,就是使用一个    Flask-Bootstrap    的扩展,它简化了集成Bootstrap 的过程;

1、安装

pip	install	flask-bootstrap

2、使用

from werkzeug.security import check_password_hash

from models import app, Todo, db, Department, User, UserLog
from flask import render_template, redirect, url_for, request, flash, session
from functools import wraps



from urllib.request import  urlopen
import  json

# 通过ip获取该IP的所在城市和国家;
def get_ip_area(ip):
    # 构造url地址, 使用淘宝的API接口
    url = 'http://ip.taobao.com/service/getIpInfo.php?ip=%s' %(ip)
    # 获取页面返回的内容()
    json_data =  urlopen(url).read().decode('utf-8')
    # 将json格式的数据转换为字典格式;
    s_data = json.loads(json_data)
    country = s_data['data']['country']
    if country == 'XX':
        country = ''
    city = s_data['data']['city']
    if city == 'XX':
        city = ''
    return country+city




# 编写一个判断用户是否登录的装饰器
def is_login(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        # 判断不在session(会话)中, 跳转到登录页面;
        if not "user" in session:
            return redirect(url_for('login'))
        return f(*args, **kwargs)

    return wrapper


@app.route('/list//')
@app.route('/list/')
@is_login
def list(page=1):
    todos = Todo.query.paginate(page, per_page=5)
    parts = Department.query.all()
    return render_template('list.html', todos=todos, parts=parts)


@app.route('/done//')
@is_login
def done(todo_id):
    todo = Todo.query.filter_by(id=todo_id).first()
    todo.status = True
    try:
        db.session.add(todo)
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        return e
    return redirect(url_for('list', page=1))


@app.route('/undone//')
@is_login
def undone(todo_id):
    todo = Todo.query.filter_by(id=todo_id).first()
    todo.status = False
    try:
        db.session.add(todo)
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        return e
    return redirect(url_for('list', page=1))


@app.route('/delete///')
@is_login
def delete(todo_id, page=1):
    u = Todo.query.filter_by(id=todo_id).first()
    db.session.delete(u)
    db.session.commit()
    return redirect(url_for('list', page=page))


@app.route('/add/', methods=['POST'])
@is_login
def add():
    # 通过request获取 表单提交的内容;
    data = request.form
    name = data['todo_name']
    part = data['part']
    # 实例化对象
    todo = Todo(name=name, department_id=part)
    db.session.add(todo)
    db.session.commit()
    return redirect(url_for('list'))


@app.route('/login/', methods=['POST', 'GET'])
def login():
    # 判断用户的HTTP请求方法
    if request.method == 'POST':
        # 1). 接收提交的数据;
        data = request.form
        name = data['name']
        pwd = data['pwd']

        # 2). 判断用户名是否存在, 密码是否正确?
        u = User.query.filter_by(name=name).first()

        if u and check_password_hash(u.pwd, pwd):
            session['user'] = u.name
            session['user_id'] = u.id
            userlog = UserLog(
                user_id=u.id,
                ip=request.remote_addr,
                area=get_ip_area(request.remote_addr)
            )
            db.session.add(userlog)
            db.session.commit()
            return redirect(url_for('list'))
        else:
            # flash: 消息闪现,
            # 如何在前端html页面显示? Jinja2 get_flash_messages()
            # 返回的是一个列表, 想依次显示内容, 需要用到for循环;
            flash('用户或者密码不正确!')
            return redirect(url_for('login'))
    else:
        return render_template('login.html')


@app.route('/logout/')
def logout():
    # 将会话中的key值弹出;
    session.pop('user', None)
    session.pop('user_id', None)
    # 注销跳转到登录页面, 或者公共首页
    return redirect(url_for('login'))


@app.route('/')
@app.route('/')
def index(page=1):
    parts = Department.query.all()
    untodos = Todo.query.filter_by(status=False).paginate(page, per_page=5)
    return render_template('index.html', parts=parts, untodos=untodos)


#
#
# #
# @app.route('/add/', methods=['POST'])
# def add():
#     form = request.form
#     print(form)
#     name = form['content']
#     part = form['part']
#     todo = Todo(name = name, department_id=part)
#     db.session.add(todo)
#     db.session.commit()
#     todos = Todo.query.all()
#     return redirect(url_for('list'))


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5008)

初始化Flask-Bootstrap之后,我们的模板通过继承一个基模板,即bootstrap/base.html ,就可以使用Bootstrap了。
新建一个templates文件夹,在该文件夹中添加一个base.html的文件,代码如下: 

{% extends 'bootstrap/base.html' %}


{% block title %}
    首页
{% endblock %}


{% block styles %}
    {{ super() }}
    
    
{% endblock %}





{% block navbar %}
    
{% endblock %}


{% block content %}


{% endblock %}

index.html

{% extends 'base.html' %}


{% block content %}
    
部门 {% for part in parts %} {{ part.name }}     {% endfor %}
任务类型 已完成    

紧急任务

注意: 登录后进行操作
{% for todo in untodos.items %} {% endfor %}
编号 任务内容 创建时间 状态 所属部门 删除
{{ todo.id }} {{ todo.name }} {{ todo.add_time }} {# 有按钮 : 如果状态为True: 则返回完成的按钮; #} {% if todo.status %} 已完成 {% else %} {# {{ url_for('done', todo_id=todo.id) }} ======== '/done/1/' #} 未完成 {% endif %} {{ todo.department.name }} 删除
{# 分页信息#} {% from 'macro/page.html' import paginate %} {{ paginate('list', untodos) }}
{% endblock %}

list.html

{% extends 'base.html' %}
{% block title %}用户显示{% endblock %}
{% block logout %}   
  • 登出
  • {% endblock %} {% block content %}





    {# 添加框 #}
    {# 选择框 #}
    {# select标签的name属性是为了将用户选择的数据传递给后台;作为key值; option的value属性, 是传递用户选择的值; #}
    {# 添加的按钮 #}
    {#
    #} {#
    #} {#
    #} {#
    #} {#
    #} {#
    #} {# #} {#
    #} {##} {#
    #} {##} {# #} {##} {#
    #} {# #} {#
    #} {##} {#
    #}

    任务显示

    {% for todo in todos.items %} {% endfor %}
    编号 任务内容 创建时间 状态 所属部门 删除
    {{ todo.id }} {{ todo.name }} {{ todo.add_time }} {# 有按钮 : 如果状态为True: 则返回完成的按钮; #} {% if todo.status %} 已完成 {% else %} {# {{ url_for('done', todo_id=todo.id) }} ======== '/done/1/' #} 未完成 {% endif %} {{ todo.department.name }} 删除
    {# 分页信息#} {% from 'macro/page.html' import paginate %} {{ paginate('list', todos) }}
    {% endblock %}

    login.html

    {% extends 'base.html' %}
    {% block title %}用户登录{% endblock %}
    
    
    
    {% block content %}
        

    用户登录

    {% for error in get_flashed_messages() %}

    {{ error }}

    {% endfor %}
    {% endblock %}

     上面的所有的页面都继承与base.html页面,而base.html就是继承于'bootstrap/base.html',

    'bootstrap/base.html'就是官方为我们提供的模板,这个模板中有帮我们“挖的坑“我们只需要将其填补上就可以比如

    {% extends 'bootstrap/base.html' %}  继承于哪里

    {% block title %} 首页 {% endblock %}   浏览器上的头部信息

    {% block content %}...... {% endblock %} 正文部分的样式

    我们也可以自己刨坑和填坑前面的文章中我们也提到过相关内容:

    模板继承语法:
        1. 如何继承某个模板?
                {% extends "模板名称" %}
        
        
        2. 如何挖坑和填坑?
        挖坑: 
            {% block 名称 %}
            
                默认值
            
            {% endblock %}
            
            
        填坑: 
            {% block 名称 %}
            
            {% endblock %}
            
        
        3. 如何调用/继承被替代的模板?
            挖坑: 
            {% block 名称 %}
            
                默认值
            
            {% endblock %}
            
            
        填坑: 
            {% block 名称 %}
                #如何继承挖坑时的默认值?
                {{ super() }}
                
                # 后面写新加的方法.
                ........   
            {% endblock %}
            

     

     

    你可能感兴趣的:(Python_day25--Flask插件:Flask-SQLAlchemy、Flask-Bootstrap)