个人博客Demo: link.
GitHub项目完整链接:link
回顾上一节主要讲了搭建个人博客之前开发环境的配置,主要囊括三个方面:
- 安装pipenv,设置虚拟环境,安装相关依赖包
- pycharm开发工具的环境配置
- MySQL的安装
2.1.1 博客结构
- 功能结构大致如图:
- 用思维导图表示了文件结构,如图:
2.1.2 编写配置文件:settings.py
基础配置类:
- 用os.getenv从环境变量中获取程序密钥SECRET_KEY(用于cookies,session等加密),同时在第二参数设置默认值
- 设置数据库的相关配置参数
- 设置CKEditor的相关配置,如CKEDITOR_ENABLE_CSRF = True开启CSRF保护
- 博客自定义参数,如:博客文章每页显示数量设置,文件上传格式限制等
开发配置类:
- 继承基本配置类,配置连接MySQL数据库的URI路径
- MySQL URI配置方法:‘mysql+pymysql:// mysql用户名 : mysql密码 @ 端口值,默认:localhost:3306 / mysql里创建的数据库名’
生产配置类:
- 继承基本配置类,从环境变量DATABASE_URL中获取数据库URI
配置映射字典方便加载配置
代码如下:
import os
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) # 根目录
class BaseConfig(object):
SECRET_KEY = os.getenv('SECRET_KEY', 'this is secret') # 获取程序密钥
SQLALCHEMY_TRACK_MODIFICATIONS = False # 关闭数据库警告信息
SQLALCHEMY_RECORD_QUERIES = True # 启用查询记录
CKEDITOR_ENABLE_CSRF = True # 开启CSRF保护
CKEDITOR_FILE_UPLOADER = 'admin.upload_image' # cke图片上传视图函数路径(需编写)
BLOG_POST_PER_PAGE = 7 # 每页文章数
BLOG_MANAGE_POST_PER_PAGE = 15 # 后台管理界面每页文章数
BLOG_COMMENT_PER_PAGE = 10 # 每页评论数
BLOG_MANAGE_COMMENT_PER_PAGE = 15 # 后台管理每页评论数
BLOG_UPLOAD_PATH = os.path.join(basedir, 'uploads') # 上传文件夹路径
BLOG_ALLOWED_IMAGE_EXTENSIONS = ['jpg', 'png', 'gif', 'jpeg'] # 上传图片支持格式
class DevelopmentConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:hejian1363531=@localhost:3306/blogdb'
class ProductionConfig(BaseConfig):
SQLALCHRMY_DATABASE_URI = os.getenv('DATABASE_URL')
# 配置映射字典,方便调用
config = {
'development': DevelopmentConfig,
'production': ProductionConfig
}
2.1.3 拓展实例化: extensions.py
在extensions.py文件中进行拓展类的实例化操作,而拓展的初始化操作在__init__.py构造文件中声明。目的是创建可以全局使用的拓展对象如:db = SQLAlchemy()。
代码如下:
from flask_sqlalchemy import SQLAlchemy
from flask_ckeditor import CKEditor
from flask_moment import Moment
from flask_bootstrap import Bootstrap
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
bootstrap = Bootstrap()
db = SQLAlchemy()
moment = Moment()
ckeditor = CKEditor()
login = LoginManager()
csrf = CSRFProtect()
2.1.4 创建数据库模型: models.py
在博客的数据库模型中我们需要创建四张表分别为:
- 管理员(Admin) 囊括主键,用户名,登录密码以及博客设置方面的博客标题,关于信息等字段。需要注意的是Admin模型中并不直接存储用户密码而是存储相应密码hash值,直接存储密码非常危险,通过定义set_password()和validate_password()两个方法对原始密码加密成hash值,在验证时也通过比对hash值进行用户验证
- 分类(Category) 包括主键及分类名字段,分类与文章之间是一对多关系
- 文章(Post) 包括文章标题,内容,时间戳等字段,文章和评论是一对多关系
- 评论(Comment) 包括评论作者,评论内容时间戳等字段,回复也是评论的一种,在评论模型内建立邻接列表关系(即同一模型内的一对多关系)
代码如下:
from flask_moment import datetime
from Blog.extensions import db
from flask_login import UserMixin
# 管理员类(存储用户名,密码hash值,博客相关设置)
class Admin(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20)) # 用户名(用于登录)
password_hash = db.Column(db.String(128)) # 存储密码hash值
# 博客设置
blog_title = db.Column(db.String(60)) # 博客标题
name = db.Column(db.String(30)) # 用户昵称(评论中显示)
about = db.Column(db.Text)
def set_password(self,password): # 将输入的密码转换成hash值进行加密
self.password_hash = generate_password_hash(password)
def validate_password(self, password): # 对输入的密码转换成hash值跟存储的hash值进行比对
return check_password_hash(self.password_hash, password)
# 分类
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True) # 分类名不允许重复,unique=True
# back_populates为SQLAlchemy的关系函数参数,用于定义反向引用,建立双向关系,在关系的另一侧也必须显式定义关系属性
posts = db.relationship('Post', back_populates='category') # 集合关系属性
# 文章
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(30))
timestamp = db.Column(db.DateTime, default=datetime.utcnow) # 时间戳
body = db.Column(db.Text)
can_comments = db.Column(db.Boolean, default=True) # 文章能否评论开关
category_id = db.Column(db.Integer, db.ForeignKey('category.id')) # 将category_id设置为外键
category = db.relationship('Category', back_populates='posts') # 标量关系属性
# 设置集合关系属性,cascade设置级联操作,文章删除,文章下的评论也删除
comments = db.relationship('Comment', back_populates='post', cascade='all, delete-orphan')
# 评论
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
author = db.Column(db.String(20))
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
body = db.Column(db.Text)
from_admin = db.Column(db.Boolean, default=False) # 判断评论是否来自管理员
reviewed = db.Column(db.Boolean, default=False) # 判断评论是否已读
post_id = db.Column(db.Integer, db.ForeignKey('post.id')) # 将文章主键id设置为外键
post = db.relationship('Post', back_populates='comments') # 标量关系属性
# 建立邻接列表关系(在同一个模型内的一对多关系)
replied_id = db.Column(db.Integer, db.ForeignKey('comment.id')) # 在replied设置外键(comment.id)
# replied表示被回复评论的标量关系属性, remote_side=[id]将id字段定义为关系远程侧
replied = db.relationship('Comment', back_populates='replies', remote_side=[id])
replies = db.relationship('Comment', back_populates='replied', cascade='all') # 集合关系
2.1.5 生成虚拟数据 fakes.py
生成虚拟数据的目的是为了方便测试博客网站的功能
fakes.py生成管理员信息,分类,文章以及评论(包括游客评论,未读评论,管理员评论以及回复)
代码虽然不短,但实际内容比较简单,参考代码里的注释和models.py很容易理解,不再赘述
代码如下:
import random
from Blog.models import Admin, Category, Post, Comment
from Blog.extensions import db
from faker import Faker
from sqlalchemy.exc import IntegrityError
fake = Faker() # 实例化Faker
def fake_admin():
# 设置管理员用户名密码等
admin = Admin(
username='nidemingzi',
blog_title="nidemingzi'blog",
name='一头特立独行的猪',
about="Welcome to my personal website,um,i ,likes uses python to coding"
)
admin.set_password('helloflask') # 调用Admin类下的ser_password()方法设置密码
db.session.add(admin) # 添加到数据库临时会话
db.session.commit() # 提交数据库会话
def fake_category(count=5):
# 定义默认分类
category = Category(name='Default')
db.session.add(category)
# 迭代生成5个虚拟分类
for i in range(count):
category = Category(name=fake.word())
db.session.add(category)
# 若随机生成分类名重复则回滚,取消添加到category的临时会话
try:
db.session.commit()
except IntegrityError:
db.session.rollback()
def fake_post(count=25):
for i in range(count):
post = Post(
title=fake.sentence(),
body=fake.text(2000),
category=Category.query.get(random.randint(1, Category.query.count())), # 文章随机分配到某个分类
timestamp=fake.date_time_this_year()
)
db.session.add(post)
db.session.commit()
def fake_comment(count=150):
for i in range(count):
comment = Comment(
author=fake.name(),
body=fake.sentence(),
timestamp=fake.date_time_this_year(),
post=Post.query.get(random.randint(1, Post.query.count())), # 评论随机分配到某篇文章中
reviewed=True
)
db.session.add(comment)
db.session.commit()
# 生成管理员评论
salt = int(0.1*count)
for i in range(salt):
comment = Comment(
author='一头特立独行的猪',
from_admin=True,
reviewed=True,
body=fake.sentence(),
timestamp=fake.date_time_this_year(),
post=Post.query.get(random.randint(1, Post.query.count()))
)
db.session.add(comment)
db.session.commit()
# 生成未读评论
for i in range(salt):
comment = Comment(
author=fake.name(),
body=fake.sentence(),
timestamp=fake.date_time_this_year(),
replied=Comment.query.get(random.randint(1, Comment.query.count())),
post=Post.query.get(random.randint(1, Post.query.count())),
reviewed=False
)
db.session.add(comment)
db.session.commit()
# 生成回复
for i in range(salt):
comment = Comment(
author=fake.name(),
body=fake.sentence(),
timestamp=fake.date_time_this_year(),
reviewed=True,
replied=Comment.query.get(random.randint(1, Comment.query.count())),
post=Post.query.get(random.randint(1, Post.query.count()))
)
db.session.add(comment)
db.session.commit()
后面会介绍__init__.py构造文件如何程序实例以及函数的注册,forms.py编写表单等内容