Python中使用Flask搭建博客网站(第2节)


在上一节教程中,我们已经能够使用Flask框架创建一个简单的Web网站应用,但是这个Web网站应用比较简单,只输出静态的文字。本节教程中,我们将介绍如何使用Flask框架搭建一个简易的动态博客网站。我们可以使用数据库来存储数据,从而更好地管理Web博客网站。

1、安装所需的第三方库

在开始搭建博客网站之前,我们需要安装好所需的第三方库。通过终端分别运行以下pip命令安装第三方库:

pip install flask
pip install Flask-SQLAlchemy
pip install Flask-Login

不懂得安装第三方库,可以参考本教程第10章中第9节和第10节里面的内容。以上3个库中,Flask-SQLAlchemy库是一个Flask扩展库,用于在Flask应用程序中简化与SQL数据库的交互。它提供了一个集成的ORM(对象关系映射)工具,使得在Flask应用程序中进行数据库操作更加方便和高效。Flask-Login库是一款专门用于Flask框架的用户认证插件,通过它我们可以轻松地实现用户登录和会话管理功能,Flask-Login提供了一个名为LoginManager的核心类,用于管理用户登录和认证。

2、创建Flask项目

创建Flask项目通俗来说就是创建一个文件夹,用一个文件夹把一系列Python代码文件包裹起来,这个文件夹就可以看做一个Flask项目。

首先,我们在Pyhint编辑器的默认开发目录“Pyhint\Learn-Python\test”下,创建一个Flask项目文件夹blog,然后进入blog文件夹创建如下文件及文件夹:

Pyhint\Learn-Python\test\blog
├── app.py
├── templates
      ├── index.html
      ├── about.html
      ├── add.html
      ├── login.html
      ├── signin.html
      └── update.html
└── static
        ├── home-bg.jpg
        └── about-bg.jpg

项目结构如下:

Pyhint\Learn-Python\test\blog\app.py

Pyhint\Learn-Python\test\blog\templates\index.html

Pyhint\Learn-Python\test\blog\templates\about.html

Pyhint\Learn-Python\test\blog\templates\add.html

Pyhint\Learn-Python\test\blog\templates\login.html

Pyhint\Learn-Python\test\blog\templates\signin.html

Pyhint\Learn-Python\test\blog\templates\update.html

Pyhint\Learn-Python\test\blog\static\home-bg.jpg

Pyhint\Learn-Python\test\blog\static\about-bg.jpg

(1)在blog文件夹内的app.py文件中添加以下代码:

app.py:

from flask import Flask, render_template, request, redirect, url_for
from flask_login import UserMixin, login_user, login_required, logout_user, current_user, LoginManager
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from flask import flash

app = Flask(__name__)
# db = SQLAlchemy(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blogs.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'thisisasecretkey'
db = SQLAlchemy(app)

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

class FLASKBLOG(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(50))
    author = db.Column(db.String(20))
    post_date = db.Column(db.DateTime)
    content = db.Column(db.Text)

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), nullable=False, unique=True)
    password = db.Column(db.String(80), nullable=False)

@app.route("/")
def hello_world():
    article = FLASKBLOG.query.order_by(FLASKBLOG.post_date.desc()).all()
    print(current_user.is_anonymous)
    if current_user.is_anonymous:
        name = "guest"
    else:
        name = current_user.username
        print("bye")
    return render_template('index.html', article=article, name=name)

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

@app.route('/addpost', methods=['POST', 'GET'])
def addpost():
    if request.method == 'POST':
        if current_user.is_anonymous:
            name = "guest"
            return render_template('login.html')
        else:
            title = request.form['title']
            author = request.form['author']
            content = request.form['content']
            post = FLASKBLOG(title=title, author=author, content=content, post_date=datetime.now())
            db.session.add(post)
            db.session.commit()
            print("Done")
            return redirect(url_for('hello_world'))
    return render_template('add.html')

@app.route('/update/<int:id>', methods=['POST', 'GET'])
@login_required
def update(id):
    if request.method == 'POST':
        title = request.form['title']
        author = request.form['author']
        content = request.form['content']
        print(content)
        post = FLASKBLOG.query.filter_by(id=id).first()
        post.title = title
        post.author = author
        post.content = content
        db.session.add(post)
        db.session.commit()
        return redirect("/")
    edit = FLASKBLOG.query.filter_by(id=id).first()
    return render_template('update.html', edit=edit)

@app.route('/delete/<int:id>')
@login_required
def delete(id):
    d = FLASKBLOG.query.filter_by(id=id).first()
    db.session.delete(d)
    db.session.commit()
    return redirect(url_for('hello_world'))

@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and user.password == password:
            login_user(user)
            return redirect(url_for('hello_world'))
        elif not user:
            flash('用户名不存在,请先注册')
        else:
            flash('密码错误')
    return render_template('login.html')

@app.route('/signin', methods=['POST', 'GET'])
def signin():
    if request.method == 'POST':
        print("hello")
        username = request.form['username']
        password = request.form['password']
        is_user = User.query.filter_by(username=username).first()
        if is_user:
            flash('该用户名已存在')
            return render_template('signin.html')
        else:
            user = User(username=username, password=password)
            db.session.add(user)
            db.session.commit()
            login_user(user)
            return redirect(url_for('hello_world'))
    return render_template('signin.html')

@app.route('/logout', methods=['GET', 'POST'])
@login_required
def logout():
    logout_user()
    return redirect(url_for('hello_world'))

# 主要驱动函数,用于判断脚本是否被直接运行,而不是被导入到另一个脚本中。
if __name__ == '__main__':
    # 模拟推送应用上下文环境
    app.app_context().push()
    # 找到所有db.model的子类,然后在数据库里面创建对应的表
    db.create_all()
    # Flask的run()方法绑定IP和端口,在本地开发服务器上运行应用程序 
    app.run(host='127.0.0.1',port=5000, debug=True)

(2)在app.py同级路径下的templates文件夹内,index.html文件中添加以下代码:

index.html:

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Flask博客</title>
</head>
<body>
<div id="part1">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
        <a class="navbar-brand" href="#">Flask博客</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll"
        aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarScroll">
        <ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
            <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="/">首页</a>
            </li>
            <li class="nav-item">
            <a class="nav-link active" href="{{ url_for('about') }}">关于我们</a>
            </li>
            <li class="nav-item">
            <a class="nav-link active" href="{{ url_for('addpost') }}" tabindex="-1">写文章</a>
            </li>
        </ul>
        <div class="mx-3">
            {% if name == "guest" %} 
            <a class="btn btn-danger" href="{{ url_for('signin') }}">注册</a>
            <a class="btn btn-danger" href="{{ url_for('login') }}">登录</a>
            {% else %} 
            <a class="btn" href="#">你好,{{name}}</a>
            <a class="btn btn-danger" href="{{ url_for('logout') }}">退出</a>
            {% endif %} 
        </div>
        </div>
    </div>
    </nav>
</div>
<div id="part2">
    <div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel">
    <div class="carousel-indicators">
        <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="0" class="active"
        aria-current="true" aria-label="Slide 1"></button>
        <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="1"
        aria-label="Slide 2"></button>
    </div>
    <div class="carousel-inner">
        <div class="carousel-item active">
            <img src="../static/home-bg.jpg" class="d-block w-100" alt="...">
            <div class="carousel-caption d-none d-md-block">
                <h5>第一张幻灯片标签</h5>
                <p>第一张幻灯片的一些代表性占位内容</p>
            </div>
        </div>
        <div class="carousel-item">
            <img src="../static/about-bg.jpg" class="d-block w-100" alt="...">
            <div class="carousel-caption d-none d-md-block">
                <h5>第二张幻灯片标签</h5>
                <p>第二张幻灯片的一些代表性占位内容</p>
            </div>
        </div>
    </div>
    </div>
</div>
    <div id="part3">
    <div class="album py-5 bg-light">
        <div class="container">
        <div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
            {% for articles in article %} 
            <div class="col">
            <div class="card shadow-sm">
                <svg class="bd-placeholder-img card-img-top" width="100%" height="225"
                xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail"
                preserveAspectRatio="xMidYMid slice" focusable="false">
                <title>占位字符</title>
                <rect width="100%" height="100%" fill="#55595c"></rect><text x="50%" y="50%" fill="#eceeef"
                    dy=".3em">缩略图</text>
                </svg>
                <div class="card-body">
                <h2 class="post-title">
                    {{ articles.title }} 
                </h2>
                <p class="card-text">{{articles.content}}</p>
                <p class="post-meta">发布者 {{ articles.author }} </p>
                <div class="d-flex justify-content-between align-items-center">
                    <div class="btn-group">
                    <!-- Check if the user is not guest, if guest "edit and delete button will be hidden" -->
                    {% if name != "guest" %} 
                    <a href="/update/{{articles.id}}" type="button" class="btn btn-sm btn-outline-secondary">编辑</a>
                    <a href="/delete/{{articles.id}}" type="button" class="btn btn-sm btn-outline-secondary">删除</a>
                    {% endif %} 
                    </div>
                    <small class="text-muted">{{ articles.post_date.strftime('%B %d, %Y') }}</small>
                </div>
                </div>
            </div>
            </div>
            {% endfor %} 
        </div>
        </div>
    </div>
    </div>
<div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
    crossorigin="anonymous"></script>
</body>
</html>

(3)在app.py同级路径下的templates文件夹内,about.html文件中添加以下代码:

about.html:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>关于我们</title>
</head>
<body>
<div id="part1">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
        <a class="navbar-brand" href="#">Flask博客</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll"
        aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarScroll">
        <ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
            <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="/">首页</a>
            </li>
            <li class="nav-item">
            <a class="nav-link" href="{{ url_for('about') }}">关于我们</a>
            </li>

            <li class="nav-item">
            <a class="nav-link" href="{{ url_for('addpost') }}" tabindex="-1">写文章</a>
            </li>
        </ul>
        <form class="d-flex">
            <input class="form-control me-2" type="search" placeholder="搜索内容" aria-label="Search">
            <button class="btn btn-outline-success" type="submit">Search</button>
        </form>
        </div>
    </div>
    </nav>
</div>
<div id="part2">
    <div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel">
        <div class="carousel-indicators">
            <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="0" class="active"
            aria-current="true" aria-label="Slide 1"></button>
            <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="1"
            aria-label="Slide 2"></button>
        </div>
    <div class="carousel-inner">
        <div class="carousel-item active">
            <img src="../static/home-bg.jpg" class="d-block w-100" alt="...">
            <div class="carousel-caption d-none d-md-block">
                <h5>第一张幻灯片标签</h5>
                <p>第一张幻灯片的一些代表性占位内容</p>
            </div>
        </div>
        <div class="carousel-item">
            <img src="../static/about-bg.jpg" class="d-block w-100" alt="...">
            <div class="carousel-caption d-none d-md-block">
                <h5>第二张幻灯片标签</h5>
                <p>第二张幻灯片的一些代表性占位内容</p>
            </div>
        </div>
    </div>
    </div>
</div>
<p class="help-block text-danger"></p>
<div class="container">
    <p class="lead ">Flask是一个轻量级的Python Web框架,注重简洁、灵活和可扩展性。它提供了核心功能,如路由、请求处理、模板渲染和会话管理,同时允许开发者根据项目需求选择适当的插件和扩展。在众多框架中,Flask以其轻量级、灵活性和易用性脱颖而出,成为许多开发者的首选。</p>
</div>
<div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
    crossorigin="anonymous"></script>
</body>
</html>

(4)在app.py同级路径下的templates文件夹内,add.html文件中添加以下代码:

add.html:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>写文章</title>
</head>
<body>
<div id="part1">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
        <a class="navbar-brand" href="#">Flask博客</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll"
        aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarScroll">
        <ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
            <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="/">首页</a>
            </li>
            <li class="nav-item">
            <a class="nav-link" href="{{ url_for('about') }}">关于我们</a>
            </li>

            <li class="nav-item">
            <a class="nav-link" href="{{ url_for('addpost') }}" tabindex="-1">写文章</a>
            </li>
        </ul>
        <form class="d-flex">
            <input class="form-control me-2" type="search" placeholder="搜索内容" aria-label="Search">
            <button class="btn btn-outline-success" type="submit">Search</button>
        </form>
        </div>
    </div>
    </nav>
</div>
<div id="part3">
    <div class="container">
    <div class="row">
        <div class="col-lg-8 col-md-10 mx-auto">
        <form name="addForm" id="addForm" method="POST" action="{{ url_for('addpost') }}" novalidate>
            <div class="control-group">
            <div class="form-group floating-label-form-group controls">
                <label>标题</label>
                <input type="text" class="form-control" placeholder="Title" name="title" id="title" required 
                data-validation-required-message="Please enter a title.">
                <p class="help-block text-danger"></p>
            </div>
            </div>
            <div class="control-group">
            <div class="form-group col-xs-12 floating-label-form-group controls">
                <label>作者</label>
                <input type="text" class="form-control" placeholder="Author" name="author" id="author" required 
                data-validation-required-message="Please enter your phone number.">
                <p class="help-block text-danger"></p>
            </div>
            </div>
            <div class="control-group">
            <div class="form-group floating-label-form-group controls">
                <label>博客内容</label>
                <textarea rows="5" class="form-control" placeholder="Blog content" name="content" id="content" required 
                data-validation-required-message="Please enter a message."></textarea>
                <p class="help-block text-danger"></p>
            </div>
            </div>
            <div class="mb-3">
            <label for="formFileSm" class="form-label">发布图片示例</label>
            <input class="form-control form-control-sm" id="formFileSm" type="file">
            </div>
            <br>
            <div id="success"></div>
            <div class="form-group">
            <button type="submit" class="btn btn-secondary" id="sendMessageButton">发布文章</button>
            </div>
        </form>
        </div>
    </div>
    </div>
</div>
<div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
    crossorigin="anonymous"></script>
</body>
</html>

(5)在app.py同级路径下的templates文件夹内,login.html文件中添加以下代码:

login.html:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>登录</title>
<style>
body {font-family: Arial, Helvetica, sans-serif;} 

/* Full-width input fields */ 
input[type=text], input[type=password] { 
width: 100%; 
padding: 12px 20px; 
margin: 8px 0; 
display: inline-block; 
border: 1px solid #ccc; 
box-sizing: border-box; 
} 

/* Set a style for all buttons */ 
button { 
background-color: #04AA6D; 
color: white; 
padding: 14px 20px; 
margin: 8px 0; 
border: none; 
cursor: pointer; 
width: 100%; 
} 

button:hover { 
opacity: 0.8; 
} 

/* Extra styles for the cancel button */ 
.cancelbtn { 
width: auto; 
padding: 10px 18px; 
background-color: #f44336; 
} 

/* Center the image and position the close button */ 
.imgcontainer { 
text-align: center; 
margin: 24px 0 12px 0; 
position: relative; 
} 

img.avatar { 
width: 40%; 
border-radius: 50%; 
} 

.container { 
padding: 16px; 
} 

span.psw { 
float: right; 
padding-top: 16px; 
} 

/* The Modal (background) */ 
.modal { 
display: none; /* Hidden by default */ 
position: fixed; /* Stay in place */ 
z-index: 1; /* Sit on top */ 
left: 0; 
top: 0; 
width: 100%; /* Full width */ 
height: 100%; /* Full height */ 
overflow: auto; /* Enable scroll if needed */ 
background-color: rgb(0,0,0); /* Fallback color */ 
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 
padding-top: 60px; 
} 

/* Modal Content/Box */ 
.modal-content { 
background-color: #fefefe; 
margin: 5% auto 15% auto; /* 5% from the top, 15% from 
the bottom and centered */ 
border: 1px solid #888; 
width: 80%; /* Could be more or less, depending on screen size */ 
} 

/* The Close Button (x) */ 
.close { 
position: absolute; 
right: 25px; 
top: 0; 
color: #000; 
font-size: 35px; 
font-weight: bold; 
} 

.close:hover, 
.close:focus { 
color: red; 
cursor: pointer; 
} 

/* Add Zoom Animation */ 
.animate { 
-webkit-animation: animatezoom 0.6s; 
animation: animatezoom 0.6s 
} 

@-webkit-keyframes animatezoom { 
from {-webkit-transform: scale(0)} 
to {-webkit-transform: scale(1)} 
} 

@keyframes animatezoom { 
from {transform: scale(0)} 
to {transform: scale(1)} 
} 

/* Change styles for span and cancel button on extra small screens */ 
@media screen and (max-width: 300px) { 
span.psw { 
    display: block; 
    float: none; 
} 
.cancelbtn { 
    width: 100%; 
} 
} 
</style>
</head>
<body>
    <div id="part1">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">Flask博客</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll"
            aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarScroll">
            <ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
                <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="/">首页</a>
                </li>
                <li class="nav-item">
                <a class="nav-link" href="{{ url_for('about') }}">关于我们</a>
                </li>

                <li class="nav-item">
                <a class="nav-link" href="{{ url_for('addpost') }}" tabindex="-1">写文章</a>
                </li>
            </ul>
            <form class="d-flex">
                <input class="form-control me-2" type="search" placeholder="搜索内容" aria-label="Search">
                <button class="btn btn-outline-success" type="submit">Search</button>
            </form>
            </div>
        </div>
        </nav>
    </div>
<!-- 显示Flash消息 -->
{% with messages = get_flashed_messages() %}
{% if messages %}
    <ul class="messages" style="text-align: center; font-size: 40px; font-weight:bold; list-style-type: none;">
        {% for message in messages %}
            <li class="{{ message.category }}">{{ message }}</li>
        {% endfor %}
    </ul>
{% endif %}
{% endwith %}
<form class="modal-content animate" action="{{ url_for('login') }}" method="POST">
    <div class="container">
        <label for="uname"><b>用户名</b></label>
        <input type="text" placeholder="请输入用户名" name="username" id="username" required>
        <label for="psw"><b>密码</b></label>
        <input type="password" placeholder="请输入密码" name="password" id="password" required>
        <button type="submit">登录</button>
        <label><input type="checkbox" checked="checked" name="remember">记住账号</label>
    </div>
    <div class="container" style="background-color:#f1f1f1">
        <button type="button" onclick="window.location.href = '/'"; class="cancelbtn">取消登录</button>
        <span class="psw">如果未注册,请先<a href="{{ url_for('signin') }}">注册</a></span>
    </div>
</form>
</body>
</html>

(6)在app.py同级路径下的templates文件夹内,signin.html文件中添加以下代码:

signin.html:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>注册</title>
<style>
body {font-family: Arial, Helvetica, sans-serif;} 

/* Full-width input fields */ 
input[type=text], input[type=password] { 
width: 100%; 
padding: 12px 20px; 
margin: 8px 0; 
display: inline-block; 
border: 1px solid #ccc; 
box-sizing: border-box; 
} 

/* Set a style for all buttons */ 
button { 
background-color: #04AA6D; 
color: white; 
padding: 14px 20px; 
margin: 8px 0; 
border: none; 
cursor: pointer; 
width: 100%; 
} 

button:hover { 
opacity: 0.8; 
} 

/* Extra styles for the cancel button */ 
.cancelbtn { 
width: auto; 
padding: 10px 18px; 
background-color: #f44336; 
} 

/* Center the image and position the close button */ 
.imgcontainer { 
text-align: center; 
margin: 24px 0 12px 0; 
position: relative; 
} 

img.avatar { 
width: 40%; 
border-radius: 50%; 
} 

.container { 
padding: 16px; 
} 

span.psw { 
float: right; 
padding-top: 16px; 
} 

/* The Modal (background) */ 
.modal { 
display: none; /* Hidden by default */ 
position: fixed; /* Stay in place */ 
z-index: 1; /* Sit on top */ 
left: 0; 
top: 0; 
width: 100%; /* Full width */ 
height: 100%; /* Full height */ 
overflow: auto; /* Enable scroll if needed */ 
background-color: rgb(0,0,0); /* Fallback color */ 
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 
padding-top: 60px; 
} 

/* Modal Content/Box */ 
.modal-content { 
background-color: #fefefe; 
margin: 5% auto 15% auto; /* 5% from the top, 15% 
from the bottom and centered */ 
border: 1px solid #888; 
width: 80%; /* Could be more or less, depending on screen size */ 
} 

/* The Close Button (x) */ 
.close { 
position: absolute; 
right: 25px; 
top: 0; 
color: #000; 
font-size: 35px; 
font-weight: bold; 
} 

.close:hover, 
.close:focus { 
color: red; 
cursor: pointer; 
} 

/* Add Zoom Animation */ 
.animate { 
-webkit-animation: animatezoom 0.6s; 
animation: animatezoom 0.6s 
} 

@-webkit-keyframes animatezoom { 
from {-webkit-transform: scale(0)} 
to {-webkit-transform: scale(1)} 
} 

@keyframes animatezoom { 
from {transform: scale(0)} 
to {transform: scale(1)} 
} 

/* Change styles for span and cancel button on extra small screens */ 
@media screen and (max-width: 300px) { 
span.psw { 
    display: block; 
    float: none; 
} 
.cancelbtn { 
    width: 100%; 
} 
} 
</style>
</head>
<body>
    <div id="part1">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">Flask博客</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll"
            aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarScroll">
            <ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
                <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="/">首页</a>
                </li>
                <li class="nav-item">
                <a class="nav-link" href="{{ url_for('about') }}">关于我们</a>
                </li>
                <li class="nav-item">
                <a class="nav-link" href="{{ url_for('addpost') }}" tabindex="-1">写文章</a>
                </li>
            </ul>
            <form class="d-flex">
                <input class="form-control me-2" type="search" placeholder="搜索内容" aria-label="Search">
                <button class="btn btn-outline-success" type="submit">Search</button>
            </form>
            </div>
        </div>
        </nav>
    </div>
<!-- 显示Flash消息 -->
{% with messages = get_flashed_messages() %}
{% if messages %}
    <ul class="messages" style="text-align: center; font-size: 40px; font-weight:bold; list-style-type: none;">
        {% for message in messages %}
            <li class="{{ message.category }}">{{ message }}</li>
        {% endfor %}
    </ul>
{% endif %}
{% endwith %}
<form class="modal-content animate" action="{{ url_for('signin') }}" method="POST">
    <div class="container">
        <label for="uname"><b>用户名</b></label>
        <input type="text" placeholder="请输入用户名" name="username" id="username" required>
        <label for="psw"><b>密码</b></label>
        <input type="password" placeholder="请输入密码" name="password" id="password" required>
        <button type="submit">注册</button>
    </div>
    <div class="container" style="background-color:#f1f1f1">
        <button type="button" onclick="window.location.href = '/'"; class="cancelbtn">取消注册</button>
    </div>
</form>
</body>
</html>

(7)在app.py同级路径下的templates文件夹内,update.html文件中添加以下代码:

update.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>更新博客</title>
</head>
<body>
    <div id="part1">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">Flask博客</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll"
                    aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarScroll">
                    <ul class="navbar-nav me-auto my-2 my-lg-0 navbar-nav-scroll" style="--bs-scroll-height: 100px;">
                        <li class="nav-item">
                            <a class="nav-link active" aria-current="page" href="/">首页</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('about') }}">关于我们</a>
                        </li>

                        <li class="nav-item">
                            <a class="nav-link" href="{{ url_for('addpost') }}" tabindex="-1">写文章</a>
                        </li>
                    </ul>
                    <form class="d-flex">
                        <input class="form-control me-2" type="search" placeholder="搜索内容" aria-label="Search">
                        <button class="btn btn-outline-success" type="submit">Search</button>
                    </form>
                </div>
            </div>
        </nav>
    </div>
    <div id="part2">
        <div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel">
            <div class="carousel-indicators">
                <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="0" class="active"
                aria-current="true" aria-label="Slide 1"></button>
                <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="1"
                aria-label="Slide 2"></button>
            </div>
        <div class="carousel-inner">
            <div class="carousel-item active">
                <img src="../static/home-bg.jpg" class="d-block w-100" alt="...">
                <div class="carousel-caption d-none d-md-block">
                    <h5>第一张幻灯片标签</h5>
                    <p>第一张幻灯片的一些代表性占位内容</p>
                </div>
            </div>
            <div class="carousel-item">
                <img src="../static/about-bg.jpg" class="d-block w-100" alt="...">
                <div class="carousel-caption d-none d-md-block">
                    <h5>第二张幻灯片标签</h5>
                    <p>第二张幻灯片的一些代表性占位内容</p>
                </div>
            </div>
        </div>
    </div>
    <p class="help-block text-danger"></p>
    <div id="part3">
        <div class="container">
            <div class="row">
                <div class="col-lg-8 col-md-10 mx-auto">
                    <form name="addForm" id="addForm" method="POST" action="/update/{{edit.id}}" novalidate>
                        <div class="control-group">
                            <div class="form-group floating-label-form-group controls">
                                <label>更新标题</label>
                                <input type="text" class="form-control" value="{{edit.title}}" name="title" id="title"
                                    required data-validation-required-message="Please enter a title.">
                                <p class="help-block text-danger"></p>
                            </div>
                        </div>

                        <div class="control-group">
                            <div class="form-group col-xs-12 floating-label-form-group controls">
                                <label>作者</label>
                                <input type="text" class="form-control" value="{{edit.author}}" name="author"
                                    id="author" required 
                                    data-validation-required-message="Please enter your phone number.">
                                <p class="help-block text-danger"></p>
                            </div>
                        </div>
                        <div class="control-group">
                            <div class="form-group floating-label-form-group controls">
                                <label>更新的博客内容</label>
                                <textarea rows="5" class="form-control"
                                    placeholder="{{edit.content}}" name="content" id="content" required 
                                    data-validation-required-message="请输入信息">
                            </textarea>
                                <p class="help-block text-danger"></p>
                            </div>
                        </div>
                        <br>
                        <div id="success"></div>
                        <div class="form-group">
                            <button type="submit" class="btn btn-secondary" id="sendMessageButton">发布文章</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <div>
    </div>
    <p class="help-block text-danger"></p>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
        crossorigin="anonymous"></script>
</body>
</html>

(8)在app.py同级路径下的static文件夹内,放入以下两个文件图片,注意,这两张图片下载后分别命名为about-bg.jpg和home-bg.jpg,并放入static文件夹内。

3、运行Flask应用程序

完成了Flask应用的基本搭建之后,就需要运行该应用程序以便在浏览器中查看它的效果。我们可以通过在终端中执行app.py来运行Flask应用程序。首先,打开Pyhint编辑器,点击Pyhint编辑器上面的“打开终端”按钮,在弹出的黑色cmd终端窗口中,输入“cd blog”然后按下Enter回车键进入blog目录,确保在终端中与app.py位于同一目录中。(注意:在终端中可以使用“cd 文件夹名”命令在目录之间移动。)如图:

最后,在终端中执行“python app.py”命令按下Enter回车键来启动Flask应用。如图:

E:\Pyhint\Learn-Python\test>cd blog

E:\Pyhint\Learn-Python\test\blog>python app.py
 * Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 297-585-820

看到上述信息,证明Flask服务器开启成功。(终端中已经提示了详细的信息,服务器地址为127.0.0.1:5000,在命令行按“CTRL+C”即可退出Flask服务器)

接下来在任意浏览器中输入http://127.0.0.1:5000/或http://localhost:5000/即可访问Flask创建的Web网站。该博客网站主要包括5个页面,分别为127.0.0.1:5000(首页)、127.0.0.1:5000/signin(注册页面)、127.0.0.1:5000/login(登录页面)、127.0.0.1:5000/about(关于我们页面)、127.0.0.1:5000/addpost(发表博客页面)。如下图:

这个时候打开项目blog目录,你会惊奇地发现,在“E:\Pyhint\Learn-Python\test\blog”的文件夹内会出现一个“instance”文件夹,并且里面有一个blogs.db文件,该文件就是Flask自动生成的,专门存储应用程序的配置文件和数据文件,包括博客用户的注册信息和博客文章数据等。

到这里,使用Flask搭建的一个简单博客网站就完成了,进入“http://127.0.0.1:5000”博客网站后,对于首次使用的用户,必须先注册然后才可以登录发布博客文章。我们还可以根据自己的需求扩展和定制Flask博客网站,可以添加更多的路由、视图函数,使用更多的模板引擎渲染动态页面,处理表单提交,连接数据库等等。

以上就是一个简单的Flask博客网站创建过程,在实际运用中可以添加更多功能和页面来构建更复杂的Web网站。在下一节教程中我们将详细讲解Flask博客网站的工作原理。