前段时间学习渗透的时候,感觉SSTI模板注入很迷糊,就想着来研究一下flask框架来加深一下SSTI模板注入的理解,然后一发不可收拾,萌生了开发一个搭建个简易博客的想法,接下来就从零开始构建一个简单的博客吧!

Hello,Flask!

flask简单来说就是一个WEB框架,那什么是WEB框架呢?在之前的学习当中,前端我们可以使用html,CSS,JavaScript,后端可以使用PHP来编写,最终实现一个WEB网站,那随着时代的发展,这种设计模式慢慢变得有点落后,就出现了一个相当于管理员的应用,也就是前面讲到的WEB框架,它可以帮助你完成很多繁琐的事情,如:路由,响应,请求等等功能,让你更加的注重网站的开发,剩下的事情统统都交给它就好了!

现在的WEB框架已经演变出了一个完整的体系—-MTV(Model Template View)也就是模型,模板和视图,这是WEB框架的核心,一切都是围绕这三个部分来构建的,模型是指数据库模型,用于处理后端的数据,模板是指前端页面的展示,它和URL的联系是通过视图函数来实现的,也可以称他为路由,后面会慢慢的认识到这个体系的精巧!

最后,其实在python的世界里不单单只有flask这一个框架,比较常见的还有tornado,Django,flask与它们的区别就在于量级和线程,也就是说flask相较于它们来说比较简单,更容易上手一些,所以研究web框架的第一站我就选择了flask来进行研究!

虚拟环境

在创建第一个flask应用程序之前,还得介绍一个环境,就是虚拟环境,为啥需要这个环境呢?其实很简单,就是防止函数库冲突,我们都知道在写一些比较大型的python应用程序的时候都需要导入库,那问题就来了,今天这个项目需要flask1.0来写,我们就去装一个flask1.0,明天那个项目需要flask1.1.2来写,我们就去装一个flask1.1.2,可能在某些读者看来这无伤大雅,只是一个卸载和安装的问题,可是一个项目并不可能只导入一个库,当几十个库都需要更换的时候,这个工作量就显得有点大了!

那如何使用和安装一个虚拟环境呢?这也很简单,在开发的过程当中我们都会使用IDE,也就是学python都会用的Pycharm,它其实已经为减掉了大部分的繁琐工作,当New Project的时候就已经创建好了一个虚拟的环境,你也可以选择下面的Existing interpreter来加载已经存在的虚拟环境,之后你所安装的所有库都归这个虚拟环境所有!(Pycharm社区版没用New Flask Project的功能,需要自行pip安装)

若是需要通过命令行来创建虚拟机环境可以参照下面的链接:

linux创建虚拟环境(python虚拟环境)

创建Linux虚拟环境的两种方式 venv 与 virtualenvwrapper 以及 Pycharm 如何连接 Linux虚拟环境

项目结构

创建完项目之后,可以看到下面的包结构:

— 项目名称

​ |—- static

​ |—- templates

​ |—- app.py

static通常来说都是存放CSS,JavaScript脚本,图片等等,templates存放html,app.py就为flask项目的启动文件,在编写代码的时候最好按照这种模式来存放文件,因为在后面可以看到在一些函数中已经做了类似宏定义的东西,轻易打破这种设定会导致一下奇奇怪怪的问题产生….

运行Flask服务器!

点开app.py其实里面已经写好了一段代码,这个代码的意思后面再慢慢道来,我们先将其运行起来,可以看到在下面出现一个链接:http://localhost:5000/,点开之后,一个Hello World!就出现在屏幕上!!!这算是我们的第一个flask程序!

为什么要传递__name__

在刚刚的代码中,第一行就是下面这个代码,它去创建一个类,并将__name__传递进去,我们都知道__name__是当前模块的名字

__name__的用法

这到底在干嘛呢?按住Ctrl点击Flask,我们进源码一探究竟!

1
app = Flask(__name__)

进来看到Flask的描述,下面摘取一些比较重要的描述:

The flask object implements a WSGI application and acts as the central object. It is passed the name of the module or package of the application. Once it is created it will act as a central registry for the view functions, the URL rules, template configuration and much more.

……

The idea of the first parameter is to give Flask an idea of what belongs to your application. This name is used to find resources on the filesystem, can be used by extensions to improve debugging information and a lot more.

So it’s important what you provide there. If you are using a single module, __name__ is always the correct value. If you however are using a package, it’s usually recommended to hardcode the name of your package there.

……

Why is that? The application will work even with __name__, thanks to how resources are looked up. However it will make debugging more painful. Certain extensions can make assumptions based on the import name of your application. For example the Flask-SQLAlchemy extension will look for the code in your application that triggered an SQL query in debug mode. If the import name is not properly set up, that debugging information is lost. (For example it would only pick up SQL queries in yourapplication.app and not yourapplication.views.frontend)

翻译一下可知:

  • Flask启动了WSGI一个应用
  • Flask是通过第一个参数,也就是__name__来查找资源
  • 如果没有传递__name__会丢失很多调试信息

到这也就知道为啥要传递__name__这个参数了,Flask服务器是通过模块的名字来定位程序的位置的,并作为起点来计算绝对路径,往下翻看到__init__

1
2
3
4
5
6
7
8
9
10
11
12
13
def __init__(
self,
import_name: str,
static_url_path: t.Optional[str] = None,
static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
static_host: t.Optional[str] = None,
host_matching: bool = False,
subdomain_matching: bool = False,
template_folder: t.Optional[str] = "templates",
instance_path: t.Optional[str] = None,
instance_relative_config: bool = False,
root_path: t.Optional[str] = None,
)

除了import_name其他变量都是有默认值的,看到下面的static_folder和template_folder,回过头想想当初的项目结构,是不是就明白为啥需要遵循那样的规则?

配置调试模式

在不做任何配置的时候,Flask是默认不开启调试模式的,可以通过打印app.config来查看:

开启的方法有三种:

  • 直接在Pycharm的配置文件中开启即可:Run -> Edit Configurations… -> FLASK_DUBUG
  • 在app.py加入配置:app.config[‘DUBUG’] = True
  • 修改启动的参数:app.run(debug=True)

讲完了开启的方法,那开启了调试模式有啥用呢?

  • 即平时修改代码后项目不会自动更新代码,如果开启调试模式之后,项目检查到被修改就会自动重新启动更新,很方便有没有!
  • 同时在发生服务器中有代码错误的时候,会将错误信息展示出来,方便开发人员定位错误

为了防止项目耦合,在创建一个config.py文件存放配置信息:

1
2
3
class Config(object):
ENV = "development"
DUBUG = True

在app.py导入配置文件:

1
2
3
4
from config import Config

...
app.config.from_object(Config)

路由

路由顾名思义就是指路的,当浏览器访问http://localhost:5000/的时候发送了什么呢?答案很简单,当浏览器访问的时候就意味着它向Flask服务器发送了请求,Flask服务器根据URL找到对应的处理函数,然后执行,最后返回,那么问题又来了:Flask服务器是这么找到对应的处理函数的呢?没错!就是装饰器,也就是下面的app.route里面的参数,里面是“/”,就是说当浏览器请求“/”的时候就会进到该装饰器底下的视图函数当中,因为函数返回的是一个字符串,然后浏览器接受Flask服务器返回的数据并进行显示,也就是之前看到的Hello World!

1
2
3
@app.route('/')
def hello_world():
return 'Hello World!'

我们已经知道浏览器会接受Flask服务器返回的数据并进行显示,刚刚返回的是字符串,那html它能成功接收并显示吗?

1
2
3
@app.route('/')
def hello_world():
return '<font color="red">Hello World!</font>'

答案肯定是可以的:

通过F12查看Response Headers中的Content-Type可以发现它是text/html,其实它本质将接收的内容转换成html然后再显示!

1
2
3
4
Content-Length: 37
Content-Type: text/html; charset=utf-8
Date: Sun, 27 Mar 2022 07:17:03 GMT
Server: Werkzeug/2.0.3 Python/3.8.2

那除了html格式还要其他格式吗?有的,也就是json格式,当返回的是一个字典的时候它为application/json格式的:

模板

在上节中,通过返回一个字符串来让浏览器渲染成text/html,所以可以直接返回一个html让浏览器渲染,但一般不会在视图函数中直接写html代码,因为当项目逐渐变大的时候,html也开始变得十分的复杂,这样就会让视图函数特别的臃肿,那么模板的概念也就引入进来了!所有的html都放在templates中,下面创建一个index.html,可以看到下面的html多了几个陌生的语法,这是动态网页的灵魂,可以通过动态传入相应的值让浏览器来渲染,用法其实很简单,(1)用来放置变量,(2)来放置控制语句,需要注意的是结尾有个(3)来标记控制语句的结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!doctype html>
<html>
<head>
{% if title %}
<title>{{ title }} - Myblog</title>
{% else %}
<title>Welcome to Myblog</title>
{% endif %}
</head>
<body>
<h1>Hi, {{ user.username }}!</h1>(2)
{% for post in posts %}(1)
<div><p>{{ user.username }} says: <b>{{ user.body }}</b></p></div>
{% endfor %}(3)
</body>
</html>

那怎么引入呢?答:render_template(),就像下面一样,刚刚说到可以传递参数进去动态渲染,user=user就是传递参数的方式

1
2
3
4
5
@app.route('/')
@app.route('/index')
def index():
user = {"username": "zyen", "body": "want to be a best hacker!"}
return render_template("index.html", user=user)

模板继承

既然开始建立模板,就不得不得提模板继承,在博客中,总有一些页面元素是一成不变的,为了解耦,我们来创建一个父模板,让剩下需要父模板中的内容的子模板继承就好了,创建一个base.html,键入下面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!doctype html>
<html>
<head>
{% if title %}
<title>{{ title }} - Myblog</title>
{% else %}
<title>Welcome to Myblog</title>
{% endif %}
</head>
<body>
<div>
Microblog:
<a href="{{ url_for('.index') }}">Home</a>
</div>
<hr>
{% block content %}{% endblock %}
</body>
</html>

一个block就是可以重写的部分,剩下的内容都会继承:

1
{% block content %}{% endblock %}

那么我们的index.html就可以通过extends来继承base.html,再在block中加上需要重写的部分

1
2
3
4
5
6
{% extends "base.html" %}

{% block content %}
<h1>Hi, {{ user.username }}!</h1>
<div><p>{{ user.username }} says: <b>{{ user.body }}</b></p></div>
{% endblock %}

重新启动服务器可看到,模板已经正常工作!

过滤器

过滤器顾名思义就说过滤某种东西的,在Flask的世界里,我们可以通过{{ (value)| (filter) }}的形式来使用过滤器,filter可以理解成一个内置的函数,就比如平常在计算某个字符串的长度的时候都会去调用length(value)来计算长度,但在模板当中却是用{{(value)| length }},所以本质上还是差不多的,下面列举一些常见的过滤器,提醒一下过滤器是可以进行嵌套的{{ (value)| (filter_1)(filter_2)(filter_3)... }},执行的顺序就是从左到右

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 把大写字母转换成小写
{{ 'HELLO' | lower}}
# 把小写转换成大写
{{ 'hello'| upper }}
# 字符串反转
{{ 'hack' | reverse}}
# 首字母大写,其余字母小写
{{ 'zyen' | capitalize }}
# 过滤html标签 em标签是斜体
{{ '<em>hellp</em>' | striptags}}
# 只显list首个元素
{{ [100,90,86] | first }}
# 只显list最后一个元素
{{ [100,90,86] | last }}
# 显示一个list的长度
{{ [1,2] | length }}
# 对list所有元素求和
{{ [100,200] | sum }}
# 对于list进行排序
{{ [4,5,4,6,2,3,7] | sort}}
# 链调用过滤器,按顺序执行
{{ 'abc' | reverse | upper }}

那既然过滤器相当于python中的内置函数,所以过滤器也是可以自定义过滤器的,自定义过滤器的方法有两种:

  • 通过装饰器来添加
1
2
3
4
@app.template_filter('replace_strings')
def replace_hello(value):
value_tmp = value.replace('best boy', 'hacker')
return value_tmp
  • 通过app的方法来替换内置的方法
1
2
3
4
5
6
def replace_hello(value):
value_tmp = value.replace('best boy', 'hacker')
return value_tmp

# add_template_filter的参数一是调用的视图函数,参数二是替换的内置方法
app.add_template_filter(replace_hello, "replace")

在模板中调用刚刚定义好的视图函数:

1
2
3
4
5
6
7
8
9
{% extends "base.html" %}

{% block content %}

<h1>Hi,{{ user.username }}</h1>
{# 传递user = {"username": "zyen", "post": "i am best boy"}进来渲染 #}
{{ user.username }} say: {{ user.post | replace }}<br>

{% endblock %}

执行后看到i am best boy被替换成i am hacker,其实自定义过滤器的用法就在于此,当某个用户可控的输入中,我们需要进行更加细致的过滤的时候,可以通过自定义一个正则过滤的过滤器来对用户的输入进行过滤!

模型

终于来到模型,我们的网站不可能每次启动都需要重新输入数据才能使用,总得将数据持久化,所有这节来讲讲数据库!Flask并没有内置数据库,目的也是为了轻量级的考虑,所以它得引用外部的数据库扩展,可选的数据库有MySQL,PostgreSQL和SQLite,这里我使用的是MySQL,SQLite参考下面的文章:

SQLite配置指南

配置

使用MySQL之前需要添加一些配置才能愉快的使用数据库,首先需要打通python和数据库的联系,这里用的是MySQL,所以pymysql首当其冲,接下来是Flask和python的联系,就得用到Flask的核心插件Flask-SQLAlchemy,这个插件封装了一个很有用的功能就是ORM(Object Relational Mapping,对象关系映射),用简单的话说就是可以用类,对象来表示数据库中的一张表,现在不理解没关系,等下就会明白它的含义!最后的最后是一个管理工具—-Flask-Migrate,是SQLAlchemy的一个数据库迁移框架,它可以通过命令行很方便的去操作数据库,它们的关系如下:

在根目录创建config.py写入一系列的配置,为了调用方便,定义一个Config类,这样

1
2
3
4
5
6
class Config(object):
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:(数据库密码)@127.0.0.1/(数据库名)"
SQLALCHEMY_TRACT_MODIFICATIONS = False
# 启动调试模式
ENV = "development"
DEBUG = True

app/__init__.py中添加下面的代码生效配置:

1
2
3
4
5
from config import Config

...
app.config.from_object(Config)
...

由于版本问题导致会有一些奇奇怪怪的报错,下面列举一些我测试可用的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
alembic==1.4.2
click==7.1.2
colorama==0.4.4
Flask==1.1.2
Flask-Migrate==2.5.3
Flask-SQLAlchemy==2.4.1
greenlet==1.1.2
itsdangerous==1.1.0
Jinja2==2.11.2
Mako==1.1.2
MarkupSafe==1.1.1
PyMySQL==1.0.2
python-dateutil==2.8.2
python-editor==1.0.4
six==1.14.0
SQLAlchemy==1.3.17
Werkzeug==1.0.1

保存到名为requirements.txt中并放到项目下,在虚拟环境命令行中输入下面的命令进行安装

1
pip install -r requirements.txt

app/__init__.py添加下面的配置,需要注意的是此版本下的Migrate需要配合Manager命令行管理工具才能正常使用,高版本不需要这一步

1
2
3
4
5
6
7
8
9
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy

...
db = SQLAlchemy(app)
manager = Manager(app=app)
migrate = Migrate(app=app, db=db)
manager.add_command('db', MigrateCommand)

最后在app.py修改启动方式为manager

1
2
3
4
from app import manager

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

在虚拟环境的命令行中用运行app.py,出现下面配置则配置完成!可以看到命令行参数中有个db的选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(venv) C:\Users\zyen\PycharmProjects\myblog>python app.py
C:\Users\zyen\PycharmProjects\myblog\venv\lib\site-packages\flask_sqlalchemy\__init__.py:834: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will b
e disabled by default in the future. Set it to True or False to suppress this warning.
warnings.warn(FSADeprecationWarning(
usage: app.py [-?] {db,shell,runserver} ...

positional arguments:
{db,shell,runserver}
db Perform database migrations
shell Runs a Python shell inside Flask application context.
runserver Runs the Flask development server i.e. app.run()

optional arguments:
-?, --help show this help message and exit

在后面添加db,可以看到很多参数,这些参数就是Flask-Migrate用来操作数据库的命令,常用的为init,migrate,upgrade

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(venv) C:\Users\zyen\PycharmProjects\myblog>python app.py db
C:\Users\zyen\PycharmProjects\myblog\venv\lib\site-packages\flask_sqlalchemy\__init__.py:834: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will b
e disabled by default in the future. Set it to True or False to suppress this warning.
warnings.warn(FSADeprecationWarning(
usage: Perform database migrations

Perform database migrations

positional arguments:
{init,revision,migrate,edit,merge,upgrade,downgrade,show,history,heads,branches,current,stamp}
init Creates a new migration repository
revision Create a new revision file.
migrate Alias for 'revision --autogenerate'
edit Edit current revision.
merge Merge two revisions together. Creates a new migration file
upgrade Upgrade to a later version
downgrade Revert to a previous version
show Show the revision denoted by the given symbol.
history List changeset scripts in chronological order.
heads Show current available heads in the script directory
branches Show current branch points
current Display the current revision for each database.
stamp 'stamp' the revision table with the given revision; don't run any migrations

optional arguments:
-?, --help show this help message and exit

创建User表

刚刚讲到可以用类,对象来代替表的定义,接下来定义一个User类,在app包下创建models.py专门存放数据库表,User类继承db.Model,让它拥有db.Model的方法,如定义列和类型,查询数据库内容等,db. Column指的是数据库中的一列,db. Integer/db. String等一些系列的用法都是在定义字段的类型,下面的链接列举了全部的字段和对应的解释:

SQLAlchemy配置说明,字段类型,约束条件

1
2
3
4
5
6
7
8
9
10
11
from app import db


class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(128), index=True, unique=True)
password_hash = db.Column(db.String(128))

def __repr__(self):
return "<User {}>".format(self.username)

定义好类之后就可以可以创建ORM映射,在命令行中使用init初始化数据库,执行完成之后可以看到它创建了一个目录migrations,里面有个需要注意的目录versions,里面存放的是目前所有创建过的映射!

1
2
3
4
5
6
7
8
9
10
11
(venv) C:\Users\zyen\PycharmProjects\myblog>python app.py db init
C:\Users\zyen\PycharmProjects\myblog\venv\lib\site-packages\flask_sqlalchemy\__init__.py:834: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will b
e disabled by default in the future. Set it to True or False to suppress this warning.
warnings.warn(FSADeprecationWarning(
Creating directory C:\Users\zyen\PycharmProjects\myblog\migrations ... done
Creating directory C:\Users\zyen\PycharmProjects\myblog\migrations\versions ... done
Generating C:\Users\zyen\PycharmProjects\myblog\migrations\alembic.ini ... done
Generating C:\Users\zyen\PycharmProjects\myblog\migrations\env.py ... done
Generating C:\Users\zyen\PycharmProjects\myblog\migrations\README ... done
Generating C:\Users\zyen\PycharmProjects\myblog\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'C:\\Users\\zyen\\PycharmProjects\\myblog\\migrations\\alembic.ini' before proceeding.

接下来就创建ORM映射,可以看到在versions文件夹里面创建了一个d0b26b497fd8_.py的文件,这个其实就是本次映射所要执行的数据库命令,为什么说versions目录需要注意,就是这个原因

1
2
3
4
5
6
7
8
9
10
11
12
(venv) C:\Users\zyen\PycharmProjects\myblog>python app.py db migrate
C:\Users\zyen\PycharmProjects\myblog\venv\lib\site-packages\flask_sqlalchemy\__init__.py:834: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will b
e disabled by default in the future. Set it to True or False to suppress this warning.
warnings.warn(FSADeprecationWarning(
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'user'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
INFO [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
INFO [alembic.autogenerate.compare] Detected added table 'post'
INFO [alembic.autogenerate.compare] Detected added index 'ix_post_timeStep' on '['timeStep']'
Generating C:\Users\zyen\PycharmProjects\myblog\migrations\versions\d0b26b497fd8_.py ... done

接下来通过upgrade来更新表

1
2
3
4
5
6
7
(venv) C:\Users\zyen\PycharmProjects\myblog>python app.py db upgrade
C:\Users\zyen\PycharmProjects\myblog\venv\lib\site-packages\flask_sqlalchemy\__init__.py:834: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will b
e disabled by default in the future. Set it to True or False to suppress this warning.
warnings.warn(FSADeprecationWarning(
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> d0b26b497fd8, empty message

更新完之后,数据库已经存在User表了!!!至此Flask的核心模块已经搭建完成!接下来就可以开始在Flask大展拳脚啦!

蓝图

为了解耦,在视图上进行模块化,将同一类型的视图函数放在同一视图下,创建app/view.py,创建蓝图也很简单呐,只要调用Blueprint并将其赋值给一个变量

1
2
3
from flask import Blueprint

user_bp = Blueprint('user', __name__)

接下来就可以通过在app/user/view.py中创建user_bp视图函数

1
2
3
@user_bp.route('/login', methods=['GET', 'POST'])
def login():
pass

最后在init.py注册蓝图可以了

1
2
3
...
app.register_blueprint(user_bp)
...

博客

在正式写代码之前,要理清楚博客中数据库的表的关系,之后只要按照搭建好的模型写代码了

需要构建的模型有用户,文章,文章分类:

一对多模型 多对多
用户和文章 文章和分类

这里仅搭建一个简易的模型,读者有兴趣可以接着往里面添加模型,还可添加评论,标签等,要想实现一对多,其实只需要一个外键,就可以通过用户找到文章,多对多即在外键的基础上加一个relationship就可以实现!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Categories(db.Model):
id = db.Column(db.Integer, primary_key=True)
type_name = db.Column(db.String(20), nullable=True)
article = db.relationship("Article", backref="categories")


class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(30), nullable=True)
content = db.Column(db.Text, nullable=True)
brief = db.Column(db.Text, nullable=True)
pdatetime = db.Column(db.DateTime, default=datetime.now)
click_num = db.Column(db.Integer, default=0)
love_num = db.Column(db.Integer, default=0)
# foreignKey
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
type_id = db.Column(db.Integer, db.ForeignKey("categories.id"), nullable=True)
comment = db.relationship('Comment', backref="article")

在之前的User模型添加:

1
2
3
4
5
class User(db.Model):
...
icon = db.Column(db.String(40))
rdatetime = db.Column(db.DateTime, default=datetime.now)
articles = db.relationship('Article', backref="user")

模型也搭建完成了!

美化

因为只是建立一个简约的博客,所以用Bootstrap进行快速的开发,Flask也是有这类的插件,通过pip就能够安装:

1
pip install flask-bootstrap

重新创建base.html,它继承bootstrap的模板,这样就能直接使用bootstrap的模板了,这里可以让所有页面继承导航栏navbar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{%extends "bootstrap/base.html"%}

{% block title %} {% endblock %}

{% block styles %}

{% endblock %}

{% block navbar %}

{% endblock %}

{% block content %}

{% endblock %}

bootstrap的导航栏文档去找个模板来修改,具体美化自行定制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{%extends "bootstrap/base.html"%}
{% block title %} {% endblock %}
{% block styles %}

{% endblock %}

{% block navbar %}
<nav class="navbar navbar-default">
<div class="container-fluid">

<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">ZYen Blog&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp</a>
</div>

<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#"></span>Home </a></li>
<li><a href="#"></a>About</a></li>
<li><a href="#">Categories</a></li>
</ul>

</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{% endblock %}

{% block content %}

{% endblock %}

接下来就是慢慢写视图函数,模板渲染的事情了,大体已经完成了!

到此,整个简易的个人博客已经搭建完成,其实还是有很多东西可以往里加,包括动态,搜索功能,后台,页面美化等,后期可能会再往里面加吧!

github地址:https://github.com/ZYen12138/myblog