在上一节教程中,我们已经能够使用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博客网站的工作原理。