base.html模板
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="{{ url_for('static', filename= 'css/bootstrap.min.css') }}">
<!--这个url在这是合并相对地址的作用-->
<title>{% block title %} {% endblock %}</title>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-light bg-light">
<a class="navbar-brand" href="{{ url_for('index')}}">star-sky的博客</a>
<!--这个url在这是反向路由函数,也就是通过函数名得到与之绑定的路由地址,如果这个地址后面有<>这种变量,可以同时传参数确定-->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{url_for('new')}}">发布文章</a>
<!--这个url还是得到new函数对应的网址,这个网址可以被a链接以get方式提交-->
</li>
<li class="nav-item active">
<a class="nav-link" href="{{url_for('about')}}">关于原神</a>
</li>
</ul>
</div>
</nav>
<!--下面这个for循环我感觉就是让那个flash一闪而过的提示持久显示在上面,-->
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{% block content %} {% endblock %}
</div>
<!--模板html里面常会用a链接加url_for函数加函数名来形成一个get跳转的框架-->
<!--这下面三个我不懂js,所以只知道是为这个html模板服务-->
<script src="{{url_for('static', filename='js/jquery.slim.min.js')}}" ></script>
<script src="{{url_for('static', filename='js/popper.min.js')}}" ></script>
<script src="{{url_for('static', filename='js/bootstrap.min.js')}}" ></script>
</body>
</html>
about.html
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} 关于原神 {% endblock %}</h1>
<p>这里是七种元素交汇的幻想世界“提瓦特”。
在遥远的过去,人们藉由对神灵的信仰,获赐了驱动元素的力量,得以在荒野中筑起家园。
五百年前,古国的覆灭却使得天地变异……
如今,席卷大陆的灾难已经停息,和平却仍未如期光临。
作为故事的主人公,你从世界之外漂流而来,降临大地。你将在这广阔的世界中,自由旅行、结识同伴、寻找掌控尘世元素的七神,直到与分离的血亲重聚 </p>
<p>他熟练掌握了新时代农民工必备的多项工具,包括....</p>
{% endblock %}
<!--实际上只有block中的html语句会成为子html中的语句,才有用-->
index.html 总导航菜单显示界面
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} 欢迎来到star-sky的博客 {% endblock %}</h1>
{% for post in posts %}
<a href="{{url_for('post', post_id=post['id'])}}">
<h2>{{ post['title'] }}</h2>
</a>
<!--这个post_id=post['id'],由于post对象本质上就是一个字典,posts相当于字典元素组成的列表,这是之前一个数据库链接语句的作用
这里就可以通过文章对象获取文章id-->
<span class="badge badge-primary">{{ post['created'] }}</span>
<!--显示这个文章对象,也就是数据库中的一行被添加进去的时间-->
<a href="{{ url_for('edit', id=post['id']) }}">
<span class="badge badge-warning">编辑</span>
</a>
<!--id=post['id'],这个id就是路由装饰器的那个地址后面的<int:id>变量,要对应上哦-->
<hr>
{% endfor %}
{% endblock %}
post.html文章详情页
{% extends 'base.html' %}
{% block content %}
<h2>{% block title %} {{ post['title'] }} {% endblock %}</h2>
<span class="badge badge-primary">{{ post['created'] }}</span>
<!--文章内容-->
<p>{{ post['content'] }}</p>
<hr>
<form action="{{ url_for('delete', id=post['id']) }}" method="POST">
<input type="submit" value="删除" class="btn btn-danger btn-sm"
onclick="return confirm('确定要删除吗?')">
</form>
<!--注意数据提交和删除最好都用post,因为爬虫能够爬取get方式的
onclick="return confirm('确定要删除吗?')",这个玩意就是你点击了删除还要点个网页提示的确定才会真的提交过去删除
这个删除也可以放在菜单页也就是Index.html中,这样的话方便点,不用点了标题进来文章详情页才能删除文章-->
{% endblock %}
new.html
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} 新建文章 {% endblock %}</h1>
<form method="post">
<!--默认action='/posts/new',form在flask项目里,在html里提交不写action提交地址就是默认的返回这个页面的函数的路由地址
edit.html中那个也一样-->
<div class="form-group">
<label for="title">标题</label>
<input type="text" name="title" placeholder="标题" class="form-control"
value="{{ request.form['title'] }}"></input>
<!--这个 value="{{ request.form['title'] }}"就是把你新建文章输入的东西保存在form里面并输出显示在界面输入框这里
因为我们实际上不论是在这里形成网页内容暂存滞留还是pycharm的控制台,都无法让这个数据的更新贯穿全局,也无法有序的储存,
所以新建,编辑,删除都要用数据库语句操作数据库中的数据,要同步更新,
而这些要在主py中对应函数执行sql语句完成-->
</div>
<div class="form-group">
<label for="content">内容</label>
<textarea name="content" placeholder="文章内容"
class="form-control">{{ request.form['content'] }}</textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
{% endblock %}
edit.html
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Edit "{{ post['title'] }}" {% endblock %}</h1>
<form method="post">
<div class="form-group">
<label for="title">标题</label>
<input type="text" name="title" placeholder="标题" class="form-control"
value="{{ request.form['title'] or post['title'] }}">
</input>
<!--这个value="{{ request.form['title'] or post['title'] }}就是说如果没有编辑就还是{{post['title']}}
编辑了就是{{ request.form['title'] }},主要就是因为新建时候原来是空的不用输出post['title'],而编辑一般不是空的-->
</div>
<div class="form-group">
<label for="content">内容</label>
<textarea name="content" placeholder="Post content"
class="form-control">{{ request.form['内容'] or post['content'] }}</textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
<hr>
{% endblock %}
init_db.py
import sqlite3
# 创建数据库链接
connection = sqlite3.connect('database.db')
# 执行db.sql中的SQL语句
with open('db.sql') as f:
connection.executescript(f.read())
# connection.executescript(f.read())是把db.sql变成2进制数据传入executescript脚本函数转换成sql语句并执行
# id INTEGER PRIMARY KEY AUTOINCREMENT,这个id属性的 AUTOINCREMENT类型应该会随着文章对象的增自增,
# 但是假如排序或者删除了文章,这个id不会变,就像有记录一样,后面再增加也是在记录的基础上
# 创建一个执行句柄,用来执行后面的语句,感觉和直接connection区别不大,应该是数据库知识
cur = connection.cursor()
# 插入两条文章
cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)",
('学习Flask1', '跟star学习flask第一部分')
)
cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)",
('学习Flask2', '跟star学习flask第二部分')
)
#这里的插入其实没有意义,后面要专门有new新文章的函数
# 提交前面的数据操作
connection.commit()
# 关闭链接
connection.close()
#这个关闭链接只要用了数据库链接操作某个数据库里的内容就必须要加上
db.sql
DROP TABLE IF EXISTS posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
content TEXT NOT NULL
);
blog.py
import sqlite3
from flask import Flask, render_template, request, url_for, flash, redirect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'star_sky is fat again, 555'
#这个是一个session密钥,确保安全的应该是,我只知道不加都跑不起来,可以随便写,长点好
# 创建一个函数用来获取数据库链接,函数化
def get_db_connection():
# 创建数据库链接到database.db文件,本质上包括创建可能不存在的数据库和use数据库,有就直接use
conn = sqlite3.connect('database.db')
# 设置数据的解析方法,有了这个设置,就可以像字典一样访问每一列数据,说白了就是让文章对象成字典对象
conn.row_factory = sqlite3.Row
return conn
# 根据post_id从数据库中获取post的函数,在html中的url_for函数中是用post['id']来获取id
def get_post(post_id):
conn = get_db_connection()
post = conn.execute('SELECT * FROM posts WHERE id = ?',
(post_id,)).fetchone()
#(post_id,))这个是一个元组传参,只有一个元素记得要打逗号,只有一个文章对象就fetchone,否则就fetchall
conn.close()
return post
@app.route('/')
def index():
# 调用上面的函数,获取链接,这个index.html就是总导航菜单页面
conn = get_db_connection()
# 查询所有数据,放到变量posts中
posts = conn.execute('SELECT * FROM posts order by created desc').fetchall()
conn.close()
#把查询出来的posts传给网页
return render_template('index.html', posts=posts)
#SELECT * FROM posts order by created desc中的order by created desc就是把这些文章对象变成字典对象组成的列表之前倒序一下
#估计字典对象的键就是属性名。这里有4个
@app.route('/posts/<int:post_id>')
def post(post_id):
post = get_post(post_id)
return render_template('post.html', post=post)
# < a
# href = "{{url_for('post', post_id=post['id'])}}" >
# < h2 > {{post['title']}} < / h2 >
# < / a >
#index.html中利用标题,也就是a链接结合urlfor函数通过函数名和参数返回的url来调用post函数,返回文章详情页,最后就相当于用文章id获取文章
@app.route('/posts/new', methods=('GET', 'POST'))
def new():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
if not title:
flash('标题不能为空!')
elif not content:
flash('内容不能为空')
else:
conn = get_db_connection()
conn.execute('INSERT INTO posts (title, content) VALUES (?, ?)',
(title, content))
conn.commit()
conn.close()
return redirect(url_for('index'))
return render_template('new.html')
#也就是说get方式提交访问过来就是直接返回new.html,post就是在new.html post再次提交到这个路由地址,然后就
#拿到网页上滞留的数据进行判空,这里俩都不为空(edit时内容可以为空),再用数据库链接操作,执行sql语句进行插入添加
# conn.commit()更新数据不能少,之后就直接 return redirect(url_for('index'))固定格式重定向到总导航菜单显示页面
#methods=('GET', 'POST')不可少
@app.route('/posts/<int:id>/edit', methods=('GET', 'POST'))
def edit(id):
post = get_post(id)
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
if not title:
flash('标题不能为空!')
#这个flash就是一个网页提示,参数可以是格式字符串
else:
conn = get_db_connection()
conn.execute('UPDATE posts SET title = ?, content = ?'
' WHERE id = ?',
(title, content, id))
conn.commit()
conn.close()
return redirect(url_for('index'))
return render_template('edit.html', post=post)
@app.route('/posts/<int:id>/delete', methods=('POST',))
def delete(id):
post = get_post(id)
conn = get_db_connection()
conn.execute('DELETE FROM posts WHERE id = ?', (id,))
conn.commit()
conn.close()
flash('"{}" 删除成功!'.format(post['title']))
return redirect(url_for('index'))
@app.route('/about')
def about():
return render_template('about.html')
if __name__ =="__main__":
app.run()
项目结构
默认运行在5000端口上
另外,编辑的时候会有之前的提示。