Flask框架开发博客系统

12.1需求分析

好记星博客系统应具有以下功能:

1.完整的用户管理模块,包括用户登录和退出登陆等功能。

2.完整的博客管理模块,包括添加博客,编辑博客,删除博客等。

3.完善的会员权限管理,只有登陆的用户才能访问控制台及管理博客。

4.响应式布局,用户在Web端和移动端都能达到较好的阅读体验。

12.2系统功能设计

博客系统的功能结构主要包括两部分:用户管理和博客管理。

用户访问系统项目是,可以使用游客的身份浏览博客首页,以及博客内容。如果需要管理博客(如:添加编辑博客等),就必须先注册成为会员,登陆网站后才能执行相应的操作。

12.3系统开发必备

本系统开发环境如下:

操作系统:Windows7及以上

开发工具:Pycharm

数据库:MySQL+PyMySQL驱动

web框架:Flask

第三方模块:WTForms,passlib

12.4数据库设计

本项目采用mysql数据库,数据库名称为notebook。创建数据库代码如下:

%ignore_pre_1%

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sLzqkB98-1663828341324)(D:\CodeAndProject\PycharmProjects\flask_related\博客系统\images\1663815771(1)].jpg)

本项目主要涉及到用户和博客两部分,分别创建下表:

user:用户表,用于存储用户信息。

articles:博客表,用于存储博客信息。

创建表sql语句如下:

use notebook;

DROP TABLE IF EXISTS users;
CREATE TABLE users(
    id int(8) NOT NULL AUTO_INCREMENT,
    username varchar(255) DEFAULT NULL,
    email varchar(255) DEFAULT NULL,
    password varchar(255) DEFAULT NULL,
    PRIMARY KEY(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS artciles;
CREATE TABLE articles(
    id int(8) NOT NULL AUTO_INCREMENT,
    title varchar(255) DEFAULT NULL,
    content text,
    author varchar(255) DEFAULT NULL,
    crate_date datetime DEFAULT NULL,
    PRIMARY KEY(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VP2uvcIs-1663828341325)(D:\CodeAndProject\PycharmProjects\flask_related\博客系统\images\1663816688(1)].jpg)

users表结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8BMZs76U-1663828341326)(D:\CodeAndProject\PycharmProjects\flask_related\博客系统\images\1663816765(1)].jpg)

articles表结构如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4WCthpWP-1663828341327)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220922112019121.png)]

在本项目中使用PyMySQ驱动来操作数据库,并实现对博客的增删改查功能。每次执行数据表操作时都需要遵循如下流程:连接数据库-执行sql语句-关闭数据库。

为了复用代码,单独创建一个mysql_util.py文件,其中包括一个MysqlUtil类,用于实现基本的增删改查,代码如下:

import pymysql

import traceback
import sys

class MysqlUtil():
    def __init__(self):
"""
            初始化方法,连接数据库
"""

        host = '127.0.0.1'
        user = 'root'
        password = 'wocaonima'
        database = 'notebook'
        self.db = pymysql.connect(
            host=host, user=user, password=password, db=database
        )

        self.cursor = self.db.cursor(cursor=pymysql.cursors.DictCursor)

    def insert(self, sql):
"""
        插入数据库
        :param sql: 插入数据库是的sql语句

"""
        try:

            self.cursor.execute(sql)

            self.db.commit()
        except Exception:

            print("发生异常", Exception)
            self.db.rollback()
        finally:

            self.db.close()

    def fetchone(self, sql):
"""
        查询数据库:单个数据集
        fetchone(): 该方法获取下一个查询结果集,结果集是一个对象
        :param sql:
        :return:
"""
        try:

            self.cursor.execute(sql)
            result = self.cursor.fetchone()
        except:

            traceback.print_exc()

            self.db.rollback()
            result = 'None'

        finally:

            self.db.close()
        return result

    def fetchall(self, sql):
"""
        查询数据库:多个数据集
        fetchall():接受全部的返回结果行
        :param sql:
        :return:
"""
        try:

            self.cursor.execute(sql)
            results = self.cursor.fetchall()
        except:

            info = sys.exc_info()
            print(info[0], ":", info[1])

            self.db.rollback()
            results = 'None'
        finally:

            self.db.close()
        return results

    def delete(self, sql):
"""
        删除结果集
        :param sql:
        :return:
"""
        try:

            self.cursor.execute(sql)

            self.db.commit()
        except:
            f = open('\log.txt', 'a')
            traceback.print_exc(file=f)
            f.flush()
            f.close()

            self.db.rollback()
        finally:

            self.db.close()

    def update(self, sql):
"""
        更新结果集
        :param sql:
        :return:
"""

        try:

            self.cursor.execute(sql)

            self.db.commit()
        except:

            self.db.rollback()
        finally:

            self.db.close()

12.5用户模块设计

用户模块主要包括4部分功能:用户登录,用户注册,退出登录,用户权限管理。这里的用户权限管理是指,只有登陆后用户才能访问到某些页面(如访问台)。

用户登录功能主要用于实现网站的会员登录。用户填写正确的用户名和密码,单机”登录”按钮,即可实现会员登录。如果没有输入账户或密码,账户密码长度不正确,都会给予错误提示。

用户验证的规则较多,我们使用flask_wtf模块来验证功能。创建forms.py文件,在该文件中创建一个LoginForm类,关键代码如下:

from wtforms import Form, StringField, TextAreaField, PasswordField
from wtforms.validators import DataRequired, Length, ValidationError
from flask_wtf import FlaskForm
from mysql_util import MysqlUtil

class LoginForm(FlaskForm):
    username = StringField(
        '用户名',
        validators=[
            DataRequired(message='请输入用户名'),
            Length(max=25, min=4, message='长度在4-25个字符之间')
        ]
    )

    password = StringField(
        '密码',
        validators=[
            DataRequired(message='密码不能为空'),
            Length(max=25, min=4, message='长度在6-20个字符之间')
        ]
    )

    def validate_username(self, field):

        sql = "SELECT * FROM user WHERE username = '%s'" % field.data
        db = MysqlUtil()
        result = db.fetchone(sql)
        if not result:
            raise ValidationError("用户名不存在")

再/templates/路径下创建login.html模板文件。使用form.username和form.password显示用户名和密码元素。具体代码如下:

{% extends 'layout.html' %}

{% block body %}
<div class="container content">
    <div class="card bg-light" style="width:600px;margin:auto">
        <article class="card-body mx-auto" style="width:400px">
            <h4 class="card-title mt-3 text-center">用户登录h4>
            {% from "includes/_formhelpers.html" import render_field %}
            <form method="POST" action="">
              {{ form.csrf_token }}
              <div class="form-group">
                {{render_field(form.username, class_="form-control")}}
              div>
              <div class="form-group">
                {{render_field(form.password, class_="form-control")}}
              div>
              <div class="form-group">
                <button type="submit" class="btn btn-primary btn-block"> 登录 button>
              div>
            form>
        article>
    div>
div>
{% endblock %}

上述模板继承自layout.html:

DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>在线学习笔记title>

    <link href="{{ url_for('static', filename='css/bootstrap.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='css/clean-blog.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
  head>
  <body>
    {% include 'includes/_navbar.html' %}
    {% include 'includes/_messages.html' %}
    {% block body %}{% endblock %}
    {% include 'includes/_footer.html' %}

    <script src="{{ url_for('static', filename='js/jquery.js') }}">script>
    <script src="{{ url_for('static', filename='js/popper.js') }}">script>
    <script src="{{ url_for('static', filename='js/bootstrap.js') }}">script>
  body>
html>

layout.html中body内引入文件结构如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAHqtzmm-1663828341328)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220922124653857.png)]

_footer.html

<div class="footer">
    <div class="footer-nav">
        <a href="/index">首页a>|
        <a href="#">元素a>|
        <a href="#">元素a>|
        <a href="#">元素a>
    div>
    <div id="j_footer" class="copy">元素
        <br>元素
    div>
div>

_formhelpers.html

{% macro render_field(field) %}
  {{ field.label }}
  {{ field(**kwargs)|safe }}
  {% if field.errors %}
    {% for error in field.errors %}
      <span class="help-inline text-danger">{{ error }}span>
    {% endfor %}
  {% endif %}
{% endmacro %}

_navbar.html

<nav class="navbar navbar-expand-lg navbar-light fixed-top" id="mainNav"
      style="background: #0085a1e8;">
      <div class="container">
        <a class="navbar-brand" href="{{ url_for('index') }}">博客a>
        <button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
          Menu
          <i class="fa fa-bars">i>
        button>
        <div class="collapse navbar-collapse" id="navbarResponsive">
          <ul class="navbar-nav ml-auto">
            <li class="nav-item">
              <a class="nav-link" href="{{ url_for('index') }}">首页a>
            li>
            <li class="nav-item">
              <a class="nav-link" href="{{ url_for('about') }}">关于我们a>
            li>
            {% if session.logged_in %}
              <li class="nav-item">
                <a class="nav-link" href="/dashboard">控制台a>
              li>
              <li class="nav-item">
                <a class="nav-link" href="/logout">退出a>
              li>
            {% else %}
              <li class="nav-item">
                <a class="nav-link" href="/login">登录a>li>
            {% endif %}
          ul>
        div>
      div>
    nav>

_messages.html

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
  <div class="message-info" style="margin-top:80px">
    {% for category, message in messages %}
      <div class="alert alert-{{ category }}">{{ message }}div>
    {% endfor %}
  div>
  {% endif %}
{% endwith %}

{% if error %}
  <div class="alert alert-danger">{{error}}div>
{% endif %}

{% if msg %}
  <div class="alert alert-success">{{msg}}div>
{% endif %}

因为要引入一些js和css,所以再static文件下创建js和css文件夹,暂且先加入以下内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RnebFbgm-1663828341330)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220922124945829.png)]

3.实现登录功能

当用户填写信息登陆后,如果验证全部通过,需要将登录标识和username写入Session中,为后面判断用户是否登录做准备。此外,还需要在用户访问/login路由时,判断用户是否已将登陆。如果用户之前登陆过,则不需要登陆,否则直接跳转到登陆页面,在app.py中输入以下代码:

from flask import Flask, session, redirect, url_for, request, flash, render_template
from mysql_util import MysqlUtil
from forms import LoginForm
from passlib.hash import sha256_crypt

app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if "logged_in" in session:
        return redirect(url_for("dashboard"))

    form = LoginForm(request.form)
    if form.validate_on_submit():

        username = request.form['username']
        password_candidate = request.form['password']
        sql = "SELECT * FROM users  WHERE username = '%s'" % (username)
        db = MysqlUtil()
        result = db.fetchone(sql)
        password = result['password']

        if sha256_crypt.verify(password_candidate, password):

            session['logged_in'] = True
            session['username'] = username
            flash('登录成功!', 'success')
            return redirect(url_for('dashboard'))
        else:
            flash('用户名和密码不匹配!', 'danger')

    return render_template('login.html',form=form)

  @route('/')
def dashboard():
    return '未完待续'

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

上述代码中,首先判断logged_in是否存在于session中,如果存在,则说明用户已将登录,直接调转到控制台,否则使用form.validate_on_submit()函数验证用户提交信息是否满足谁的那个条件。通过验证过,将用户提交的密码和数据库中密码进行匹配,使用sha256_crypt.vertify()方法,第一个参数是用户输入的密码,第二个是数据库加密的密码,如果返回True,则表示密码相同,否则不同

退出登录功能的实现比较简单,只要清空登录时的session中的值即可。使用session.clear函数来实现:


@app.route('/logout/')
@is_logged_in
def logout():
    session.clear()
    flash('你已成功退出', 'success')
    return redirect(url_for('login'))

12.6博客模块设计

博客模块主要包括4部分功能:博客列表、添加博客、编辑博客、和删除博客。用户必须登录后才能执行相应的操作,所以在每一个方法前添加装饰器:

@app.route('/dashboard/')
@is_logged_in
def dashboard():
    db = MysqlUtil
    sql = "SELECT * FROM articles WHERE author = '%s' ORDER BY create_date DESC" % (session['username'])
    result = db.fetchall(sql)
    if result:
        return render_template('dashboard.html', article=result)
    else:
        msg = '暂无博客信息'
        return render_template('dashboard.html', msg=msg)

上述代码,需要注意的地方就是使用session函数来获取用户名。根据装饰器判断是否登录

接下来,创建模板文件,关键代码如下:

{% for article in articles %}
  <tr>
    <td>{{article.id}}td>
    <td>{{article.title}}td>
    <td>{{article.author}}td>
    <td>{{article.create_date}}td>
    <td><a href="edit_article/{{article.id}}" class="btn btn-info" style="float:right">Edita>td>
    <td>
      <form action="{{url_for('delete_article', id=article.id)}}" method="post">
        <input type="hidden" name="_method" value="DELETE">
        <input type="submit" value="Delete" class="btn btn-danger">
      form>
    td>
  tr>
{% endfor %}

上述代码,articles变量表示所有博客对象,通过for标签来遍历每一个博客对象。

在控制台列表页面点击”添加博客”按钮,即可进入添加博客页面,在该页面中,用户需要填写博客标题和内容:


@app.route('/add_article', methods=['GET', "POST"])
@is_logged_in
def add_article():
    form = ArticleForm(request.form)
    if request.method == "POST" and form.validate():

        title = form.title.data
        content = form.content.data
        author = session['username']
        create_date = time.shrftime("%Y-%m-%d %H:%M:%S", time.localtime())
        db = MysqlUtil()

        sql = "INSERT INTO articles(title,content,author,create_date) \
                      VALUES ('%s', '%s', '%s','%s')" % (title, content, author, create_date)
        db.insert(sql)
        flash('发布成功', "success")
        return redirect(url_for('dashboard'))
    return render_template('add_article.html',form=form)

在上述代码中,接收表单的字段只包含标题和内容,此外,还需要使用session()函数获取用户名,

使用time模块获取当前时间

在控制台列表中,编辑博客代码如下:


@app.route('/edit_article/', methods=['GET', 'POST'])
@is_logged_in
def edit_article(id):
    db = MysqlUtil()
    fetch_sql = "SELECT * FROM articles WHERE id = '%s' and author = '%s'" % (id, session['username'])
    article = db.fetchone(fetch_sql)

    if not article:
        flash('ID错误', 'danger')
        return redirect(url_for('dashboard'))

    form = ArticleForm(request.form)
    if request.method == 'POST' and form.validate():

        title = request.form['title']
        content = request.form['content']
        update_sql = "UPDATE articles SET title='%s', content='%s' WHERE id='%s' and author = '%s'" % (
            title, content, id, session['username'])
        db = MysqlUtil()
        db.update(update_sql)
        flash('更改成功', 'success')
        return redirect(url_for('dashboard'))

    form.title.data = article['title']
    form.content.data = article['content']
    return render_template('edit_article.html', form=form)

12.6.4删除博客功能实现


@app.route('/delete_article/', methods=['POST'])
@is_logged_in
def delete_article(id):
    db = MysqlUtil()
    sql = "DELETE FROM articles WHERE id = '%s' and author = '%s'" % (id, session['username'])
    db.delete(sql)
    flash('删除成功', 'success')
    return redirect(url_for('dashboard'))

TO BE CONTINUE

gitee:https://gitee.com/laoliNb666/flask-notebook.git

Original: https://blog.csdn.net/m0_58613430/article/details/126991444
Author: 小白来巡山
Title: Flask框架开发博客系统

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/748152/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球