(二)flask搭建博客系列之SQLAlchemy

本篇博客介绍flask中数据库的使用,以mysql数据库为例,请先安装好mysql数据库,并新建一个名为myflask的数据库。

1.相关库安装

pip install flask-sqlalchemy flask-migrate cymysql

2.相关代码

我们新建config.py用来存放相关配置信息,新建extensions.py用来存放相关扩展的实例,新建models.py用来存放数据库相关代码,新建utils.py用来存放一些工具函数,目录结构变更如下:

config.py中写入数据库配置信息:

class Config(object):
    SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://root:root@localhost:3306/myflask?charset=utf8'
    SQLALCHEMY_TRACK_MODIFICATIONS = True

extensions.py中创建连接数据库和迁移数据库的实例:

from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()

models.py中建立4个表,分别为管理员表、博客文章表、评论表、博客分类表,分类和文章、文章和评论都是一对多的关系,这里使用了外键:

from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash

from myblog.extensions import db


class Admin(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20))
    password_hash = db.Column(db.String(128))

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def validate_password(self, password):
        return check_password_hash(self.password_hash, password)


class Article(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(60))
    body = db.Column(db.Text)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)

    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))

    category = db.relationship('Category', back_populates='articles')
    comments = db.relationship('Comment', back_populates='article', cascade='all, delete-orphan')


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    author = db.Column(db.String(30))
    body = db.Column(db.Text)

    article_id = db.Column(db.Integer, db.ForeignKey('article.id'))

    article = db.relationship('Article', back_populates='comments')


class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), unique=True)

    articles = db.relationship('Article', back_populates='category')

__init__.py中的初始化代码修改如下:

from flask import Flask

from myblog.home.blog import blog_bp
from myblog.extensions import db, migrate
from myblog.config import Config


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

    register_blueprints(app)
    register_extensions(app)

    return app


def register_blueprints(app):
    app.register_blueprint(blog_bp)


def register_extensions(app):
    db.init_app(app)
    db.create_all(app=app)
    migrate.init_app(app, db)

在utils.py中添加利用faker库生成虚拟数据的工具函数(安装:pip install faker):

import random
from faker import Faker 
from sqlalchemy.exc import IntegrityError

from myblog.models import Admin, Article, Comment, Category
from myblog.extensions import db


fake = Faker()


def fake_all():
    fake_admin()
    fake_categories()
    fake_articles()
    fake_comments()


def fake_admin():
    if Admin.query.first():
        return 
    else:
        admin = Admin(username='admin')
        admin.set_password('admin')
        db.session.add(admin)
        db.session.commit()


def fake_categories(count=10):
    if Category.query.count() == 10:
        return 
    else:
        for i in range(count):
            category = Category(name = fake.word())
            db.session.add(category)
            try:
                db.session.commit()
            except IntegrityError:
                db.session.rollback()


def fake_articles(count=50):
    for i in range(count):
        article = Article(
            title = fake.sentence(3),
            body = fake.text(2000),
            category = Category.query.get(random.randint(1, Category.query.count())),
            timestamp = fake.date_time_this_year()
        )
        db.session.add(article)
    db.session.commit()


def fake_comments(count=200):
    for i in range(count):
        comment = Comment(
            author = fake.name(),
            body = fake.sentence(),
            article = Article.query.get(random.randint(1, Article.query.count()))
        )
        db.session.add(comment)
    db.session.commit()

然后修改blog.py中的视图函数来展示数据库中的数据:

from flask import Blueprint

from myblog.models import Article, Category, Comment
from myblog.utils import fake_all


blog_bp = Blueprint('blog', __name__)


@blog_bp.route('/')            
def hello_world(): 
    fake_all()
    article = Article.query.first()

    title = article.title
    body = article.body
    category = article.category.name

    if article.comments:
        comment = article.comments[0].body
    else:
        comment = 'no comments yet'

    return """
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>SQLAlchemy展示</title>
    </head>
    <body>
        <h1>Title:</h1>
        <p>{}</p>
        <h1>Body:</h1>
        <p>{}</p>
        <h1>Category:</h1>
        <p>{}</p>
        <h1>Comment</h1>
        <p>{}</p>
    </body>
    </html>
    """.format(title, body, category, comment)

最后启动服务 python wsgi.py 在浏览器中可以看到如下效果:

3.数据库迁移介绍

当我们对数据库中的表做了结构调整后,需要保留原数据进行更新时,可以使用flask-migrate工具很方便地进行数据库迁移的工作。例如我们在文章表中添加一个表示摘要的digest字段,之前的文章表如下图所示:

我们修改models.py中的Article文章类如下:

class Article(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(60))
    digest = db.Column(db.String(120))
    body = db.Column(db.Text)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)

    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))

    category = db.relationship('Category', back_populates='articles')
    comments = db.relationship('Comment', back_populates='article', cascade='all, delete-orphan')

我们在项目根目录myflask下执行如下命令(会自动生成migrations文件夹):

flask db init                                      # 初始化
flask db migrate -m 'add digest field in Article'  # 提交改变
flask db upgrade                                   # 正式迁移

然后可以看到数据库中的Article表已经添加了digest字段:

若迁移时发生错误,可以使用如下命令方便地进行回滚:

flask db downgrade
posted @ 2020-09-27 15:31  qxcheng  阅读(325)  评论(0编辑  收藏  举报