使用Masonite框架构建博客
构建博客
前言
本文档中的章节将包含多方面的教程。这些指导设计指引你使用Masonite从头到尾构建各式项目。
文档每个章节内容并没有细讲,我们目的是初步让你熟悉Masonite的内部工作机制。
由于本章仅设计让你尝试开始用Masonite编码,任何更多详细描述都在“提示”部分有指出。
一旦完成本教程,如果你想了解更多相关主题,建议你通过“提示”提供的链接来参考文档更多的描述。
提示区
在教程里包含了多个“提示区”。以下是不同“提示区”颜色示例:
{% hint style="success" %}
绿色“提示块”提供了当前讨论内容更详细的信息。
{% endhint %}
{% hint style="info" %}
蓝色“提示块”包含了要理解当前内容的背景知识,这是你绝对不能略过的内容。
{% endhint %}
安装
本教程假设你已经安装好了Masonite。如果你还没有安装,确保阅读安装 文档进行全新安装,并将
它跑起来。一旦你安装完成,你就可以继续阅读后面的内容了。
构建一个博客应用
我们会构建一个博客应用。我们将接触到Masonite包含的主要系统,以便让你更有信心区尝试更多高级教程,
或自己构建应用程序。
路由
通常在Masonite开发流中我们起点会先创建一个路由。所有路由都放置在routes/web.py
,很容易理解。
它们包含了请求方法和路由方法。路由将请求的URI映射到控制器。
例如,创建一个GET
路由:
{% tabs %}
{% tab title="routes/web.py" %}
from masonite.routes import Get
Get('/url', 'Controller@method')
{% endtab %}
{% endtabs %}
我们后面会再详细讨论控制器。
{% hint style="success" %}
你可以阅读 路由 文档了解更多内容
{% endhint %}
创建路由
我们准备为博客应用创建一个视图和控制器。
控制器是通过一个控制器类来实现的。这些控制器包含的方法由路由来请求,用来实现应用程序业务逻辑。
{% hint style="info" %}
如果你了解Django框架,你可以将控制器方法视为views.py
文件的函数
{% endhint %}
让我们来创建第一个路由。我们可以将所有的路由放置在routes/web.py
文件里的ROUTES
列表。你可以看到
该文件已经有一条主页路由。让我们为博客应用增加一条路由。
{% tabs %}
{% tab title="routes/web.py" %}
ROUTES = [
Get('/', 'WelcomeController@show').name('welcome'),
# Blog Routes
Get('/blog', 'BlogController@show')
]
{% endtab %}
{% endtabs %}
你可能注意到了BlogController@show
字符串。它的意思为“适用blog控制器里的show方法来呈现该路由”。
这里唯一的问题我们还没有创建blog控制器。
{% hint style="success" %}
让我们进入下一步: 第二部 - 创建我们第一个控制器
{% endhint %}
创建控制器
所有的控制器都存放在app/http/controllers
目录,Masonite建议一个文件只含一个控制器,有利于提升构建大型应用程序开发效率,大部分开发者
使用带有高级搜索功能特性的文本编辑器例如Sublime,VSCode或者Atom,这样更加快速在不同类切换。因为文件名和控制器对应,
也就更加能够快速定位到文件了。
当然,你也可以将控制器放置在任何想放置额地方。不过craft命令工具会将它们放置在不同文件中。如果你不能接受此目录行为,你也可以尝试自己
布局。
创建我们第一个控制器
就和Masonite大部分一样,你可以使用craft命令来生成一个脚手架控制器:
{% tabs %}
{% tab title="terminal" %}
$ craft controller Blog
{% endtab %}
{% endtabs %}
这将会在app/http/controllers
目录下创一个类似如下的控制器:
{% tabs %}
{% tab title="app/http/controller/BlogController.py" %}
"""A BlogController Module."""
from masonite.request import Request
from masonite.view import View
from masonite.controllers import Controller
class BlogController(Controller):
"""BlogController Controller Class."""
def __init__(self, request: Request):
"""BlogController Initializer
Arguments:
request {masonite.request.Request} -- The Masonite Request class.
"""
self.request = request
def show(self, view: View):
pass
{% endtab %}
{% endtabs %}
很简答,对吧!你应该注意到show
方法了。这些称之为“控制器方法”,类似Django的“视图”。
咱们也可以看到该show方法也被定义了在我们之前查看的路由文件中了。
返回视图
控制器中可以返回各种类型,现在我们仅尝试返回视图。在Masonite中视图就是html文件或者是“模板”。
不像其他Python框架它并不是Python对象。视图就是用户将会看到的东西。
这里比较重要,因为这次我们首次提到Python的IOC容器。我们在参数列表中指定需要一个view类,Masonite将会为我们注入。
现在我们不会关注整个控制器代码。...
区域指示当前并不过多关注此区域代码:
{% tabs %}
{% tab title="app/http/controllers/BlogController.py" %}
from masonite.view import View
...
def show(self, view: View):
return view.render('blogView')
{% endtab %}
{% endtabs %}
这里我们“类型提示”指出了View
类。Masonite成为“自动依赖注入”。如果现在你还不能理解也不用担心。
多阅读文档内容你将逐步理解其中概念。
{% hint style="success" %}
确认学习更多关于服务容器.
{% endhint %}
创建我们的视图
你可能注意到返回的blog
视图还不存在。
所有的视图都存放在resources/templates
目录。我们可以创建一个新文件resources/templates/blog.html
,
或者我也可以使用craft另外一个命令来生成该视图文件:
{% tabs %}
{% tab title="terminal" %}
$ craft view blog
{% endtab %}
{% endtabs %}
该命令会为我们创建一个空模板文件。
我们可以放入如下文本:
{% tabs %}
{% tab title="resources/templates/blog.html" %}
这是一个博客。
{% endtab %}
{% endtabs %}
然后运行服务器
{% tabs %}
{% tab title="terminal" %}
$ craft serve
{% endtab %}
{% endtabs %}
接着打开浏览器浏览 http://localhost:8000/blog
. 你将看到“这是一个博客。”出现在浏览器页面中。
身份验证
大多数应用程序都需要身份认证。Masonite提供了一个craft命令来生成身份认证系统的脚手架。通常在新建应用后就可以执行了,
因为该命令会创建一些路由和视图。
就我们博客,我们需要提供注册功能以便可以发布博客。我们可以使用如下命令来创建身份认证系统;
{% tabs %}
{% tab title="terminal" %}
$ craft auth
{% endtab %}
{% endtabs %}
执行命令后我们应该可以看到一些新文件被生成提示。你可以检查控制器目录,找到一些和注册相关的处理。
稍后我们会详细介绍创建的文件。
数据库配置
为了可以让用户注册,我们需要一个数据库。希望你本地已将安装类似MySQL或者Postgres数据库,不过这里
我们假设你并没有,所以本例中我们仅使用SQLite。
现在我们只需要更改一些环境变量以便让Masonite可以创建SQLite数据库。
这些环境变量可以在根目录的.env
文件找到。打开该文件你可以看到类似如下内容:
{% tabs %}
{% tab title=".env" %}
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=masonite
DB_USERNAME=root
DB_PASSWORD=root
{% endtab %}
{% endtabs %}
继续,我们修改数据库连接配置,将sqlite
加入到DB_CONNECTION
变量中,以及当使用数据库迁移时创建的数据库文件名,这里我使用blog.db
:
{% tabs %}
{% tab title=".env" %}
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog.db
DB_USERNAME=root
DB_PASSWORD=root
{% endtab %}
{% endtabs %}
迁移
一旦设置好环境变量,我们就可以继续迁移数据库。Masonite提供了一个开箱即用的用户表,包含了用户的基础信息。你可以在进行迁移前进行修改。
默认配置适用大部分长江,你可以在后期任意添加或者删除列。
{% tabs %}
{% tab title="terminal" %}
$ craft migrate
{% endtab %}
{% endtabs %}
这将创建用户表,还有一个迁移表用来追踪任何后期迁移操作。
创建用户
现在我们有了身份认证以及完成了迁移,现在让我们来创建第一个用户。还记得我们之前运行的craft auth
命令生成了的一些模板和控制器吗?
我们来运行服务器:
{% tabs %}
{% tab title="terminal" %}
$ craft serve
{% endtab %}
{% endtabs %}
访问 http://localhost:8000/register 并填充表单。你可以选择任意的用户名和邮箱,例如:
Username: demo
Email: demo@email.com
Password: Password123@
迁移
现在我们已经配置好了身份认证和必要的迁移,让我们来创建一些新的迁移以便存储我们的博文。
在本教程中我们的博文表应该有一些较为明显的字段。让我们来一步步学习使用Masonite创建迁移。
Craft 命令
不惊讶,我们已经有了craft命令来创建迁移。你可以阅读更多关于 数据库迁移 ,这里将会
简单介绍:
{% tabs %}
{% tab title="terminal" %}
$ craft migration create_posts_table --create posts
{% endtab %}
{% endtabs %}
该命令会构建一个创建博文表的基础迁移。按约定,表名应该使用复数(列名使用单数)。
迁移会建立在databases/migrations
目录下。让我们打开该文件从第6行开始:
{% tabs %}
{% tab title="databases/migrations/2018_01_09_043202_create_posts_table.py" %}
def up(self):
"""
Run the migrations.
"""
with self.schema.create('posts') as table:
table.increments('id')
table.timestamps()
{% endtab %}
{% endtabs %}
我们来为我们的博文表加入标题,作者和主体字段。
{% tabs %}
{% tab title="databases/migrations/2018_01_09_043202_create_posts_table.py" %}
def up(self):
"""
Run the migrations.
"""
with self.schema.create('posts') as table:
table.increments('id')
table.string('title')
table.integer('author_id').unsigned()
table.foreign('author_id').references('id').on('users')
table.string('body')
table.timestamps()
{% endtab %}
{% endtabs %}
{% hint style="success" %}
这里已经相当直译了,如果想了解更多,可以阅读 数据库迁移文档。
{% endhint %}
现在我们可以将这个迁移应用到博文表中了。
{% tabs %}
{% tab title="terminal" %}
$ craft migrate
{% endtab %}
{% endtabs %}
模型
现在我们已经完成了我们的迁移工作。现在我们来创建模型。
Masonite中模型和其他框架有一点不同。Masonite使用Orator,基于Active Record的ORM实现。这就意味着并不是通过将模型转化为迁移。
在Masonite中模型和迁移是独立的。模型负责呈现表却不管表实际是怎么样的。
创建模型
再次,我们使用craft命令来创建我们的模型:
{% tabs %}
{% tab title="terminal" %}
$ craft model Post
{% endtab %}
{% endtabs %}
注意这里我们使用单数来命名我们的模型。默认Orator会通过在追加"s"到名称中查找数据库中复数命名的表(这里是posts) 。
我们会描述如何指定其表名称。
模型创建在app/Post.py
文件中,打开类似如下:
{% tabs %}
{% tab title="app/Post.py" %}
"""A Post Database Model."""
from config.database import Model
class Post(Model):
pass
{% endtab %}
{% endtabs %}
很简单,对吧!就如前述,我们不用修改模型。模型会自动映射到迁移表。
表名称
再一次说明,表名称会使用模型名称的复数形式(通过追加"s")。如果你使用了类似不一样类似"blog"而不是"blogs"来命名的话,
我们可以手动指名:
{% tabs %}
{% tab title="app/Post.py" %}
"""A Post Database Model."""
from config.database import Model
class Post(Model):
__table__ = 'blog'
{% endtab %}
{% endtabs %}
批量赋值
Orator 出于安全措施默认不允许批量赋值。我们需要手动指明哪些字段是可以填充的:
{% tabs %}
{% tab title="app/Post.py" %}
"""A Post Database Model."""
from config.database import Model
class Post(Model):
__fillable__ = ['title', 'author_id', 'body']
{% endtab %}
{% endtabs %}
表关系
表关系非常直观。还记得我们之前创建迁移时包含了一个外键吗?我们可以在模型创建类似如下关系:
{% tabs %}
{% tab title="app/Post.py" %}
"""A Post Database Model."""
from config.database import Model
from orator.orm import belongs_to
class Post(Model):
__fillable__ = ['title', 'author_id', 'body']
@belongs_to('author_id', 'id')
def author(self):
from app.User import User
return User
{% endtab %}
{% endtabs %}
因为Masonite模型工作方式,一些模型可能会相互依赖,所以为了防止循环引用,通常最好按照如上方式进行表关系模型导入。
{% hint style="success" %}
这里我们没有过多讨论不同关系类型,想学习更多的话阅读 ORM 文档。
{% endhint %}
设计我们的博客
让我们配置一些HTML以便我们能学习更多视图是如何工作。为了不涉及太多HTML代码,这里我们只会编写一个非常基础的模板,你可以基于
所学构建更加丰富的博客模板(或者从互联网搜集)。
现在我们已经配置号了模型和迁移,现在基础工作昨晚了,可以开始创建和修改博文了。
在构建模板前我还需要检查用户是否登录。
新建博文的模板
新建博文的URL是/blog/create
,提供一个简单的表单
{% tabs %}
{% tab title="resources/templates/blog.html" %}
<form action="/blog/create" method="POST">
{{ csrf_field }}
<input type="name" name="title">
<textarea name="body"></textarea>
</form>
{% endtab %}
{% endtabs %}
注意这里有一个奇怪的{{ csrf_field }}
记号。Masonite利用该标记来生成CSRF字段进行CSRF保护。
现在因为在我们的博文表里有一个外键,所以我们在提供创建页面前要确保用户已经登录,让我们稍微改一下模板:
{% tabs %}
{% tab title="resources/templates/blog.html" %}
{% if auth() %}
<form action="/blog/create" method="POST">
{{ csrf_field }}
<label> Title </label>
<input type="name" name="title"><br>
<label> Body </label>
<textarea name="body"></textarea>
<input type="submit" value="Post!">
</form>
{% else %}
<a href="/login">Please Login</a>
{% endif %}
{% endtab %}
{% endtabs %}
auth()
是一个视图辅助函数,要么返回当前用户,要么返回None
。
{% hint style="success" %}
Masonite使用Jinja2模板引擎,如果你还不太熟悉该模板引擎。确保阅读官方文档。
{% endhint %}
静态文件
出于简单考虑,我们不会对我们的博客使用类似Bootstrap进行美化,但是了解如何在Masonite中如何使用类似CSS静态文件是很有必要的。
我们将通过为我们的博客加入一个CSS文件来学习。
首先,进入到storage/static/
目录,创建一个blog.css
文件,加入任意内容。本教程里仅将html页面背景设为灰色。
{% tabs %}
{% tab title="storage/static/blog.css" %}
html {
background-color: #ddd;
}
{% endtab %}
{% endtabs %}
现在我们可以添加到我们模板顶部:
{% tabs %}
{% tab title="resources/templates/blog.html" %}
<link href="/static/blog.css" rel="stylesheet">
{% if auth() %}
<form action="/blog/create" method="POST">
{{ csrf_field }}
<label> Title </label>
<input type="name" name="title"><br>
<label> Body </label>
<textarea name="body"></textarea>
<input type="submit" value="Post!">
</form>
{% else %}
<a href="/login">Please Login</a>
{% endif %}
{% endtab %}
{% endtabs %}
好了,静态文件很简单。了解其工作非常重要,不过在这里我们更多关注后端的东西,所以暂时忽略它。
javascript使用方式一样:
{% tabs %}
{% tab title="resources/templates/blog.html" %}
<link href="/static/blog.css" rel="stylesheet">
{% if auth() %}
<form action="/blog/create" method="POST">
{{ csrf_field }}
<label> Title </label>
<input type="name" name="title"><br>
<label> Body </label>
<textarea name="body"></textarea>
<input type="submit" value="Post!">
</form>
{% else %}
<a href="/login">Please Login</a>
{% endif %}
<script src="/static/script.js"></script>
{% endtab %}
{% endtabs %}
{% hint style="success" %}
关于关于静态文件信息,查看静态文件 文档。
{% endhint %}
博文创建控制器和Container
注意我们的URI地址为/blog/create
,我们需要指向到我们控制器方法。本例我们指向到store
方法。
让我们打开routes/web.py文件,创建新路由。仅下如下内容加入到ROUTES
列表:
{% tabs %}
{% tab title="routes/web.py" %}
from masonite.routes import Get, Post
...
Post('/blog/create', 'BlogController@store'),
{% endtab %}
{% endtabs %}
我们在控制器添加store方法:
{% tabs %}
{% tab title="app/http/controllers/BlogController.py" %}
...
def show(self, view: View):
return view.render('blog')
# New store Method
def store(self):
pass
{% endtab %}
{% endtabs %}
回到之前的表单,这里我们接收两个表单元素:title和body。我们导入Post
模型,根据输入内容创建博文。
{% tabs %}
{% tab title="app/http/controllers/BlogController.py" %}
from app.Post import Post
from masonite.request import Request
...
def store(self, request: Request):
Post.create(
title=request.input('title'),
body=request.input('body'),
author_id=request.user().id
)
return 'post created'
{% endtab %}
{% endtabs %}
注意到这里我们使用了request: Request
。它是Request
对象。它从哪里来的?这就是Masonite强大的地方,也就是我们要首次介绍到的
强大的能力来帮你从Masonite中获取对象(如这里的 Request
)。确保阅读了更多文档来理解该重要概念。
{% hint style="success" %}
阅读关于 服务容器 文档。
{% endhint %}
这里还注意到我们使用了一个input()
方法。Masonite并没有区分GET
或者POST
请求获取输入参数,统一使用该input方法方法来接收。
继续,使用craft serve跑起服务器,访问http://localhost:8000/blog
并新建一个博文。这里应该会向/blog/create
路由发起一个POST
请求,并得到
响应结果为"post created"。
显示我们的博文
让我们继续看看如何显示我们刚才新建的博文。这部分我们将创建2个新的模板用来显示所有博文和指定的单个博文。
创建模板
让我们创建2个新模板。
{% tabs %}
{% tab title="terminal" %}
$ craft view posts
$ craft view single
{% endtab %}
{% endtabs %}
让我们来显示所有的博文。
创建控制器
让我们为博文创建一个独立的控制器,与BlogController隔离开。
{% tabs %}
{% tab title="terminal" %}
$ craft controller Post
{% endtab %}
{% endtabs %}
真棒!现在我们会在show
方法来显示所有的博文,用single
方法来显示特定博文。
Show 方法
让我们找到show
方法并带有所有博文的视图:
{% tabs %}
{% tab title="app/http/controllers/PostController.py" %}
from app.Post import Post
...
def show(self, view: View):
posts = Post.all()
return view.render('posts', {'posts': posts})
{% endtab %}
{% endtabs %}
博文路由
我需要为该方法增加一条路由:
{% tabs %}
{% tab title="routes/web.py" %}
Get('/posts', 'PostController@show')
{% endtab %}
{% endtabs %}
博文视图
我们的博文视图非常简单:
{% tabs %}
{% tab title="resources/templates/posts.html" %}
{% for post in posts %}
{{ post.title }}
<br>
{{ post.body }}
<hr>
{% endfor %}
{% endtab %}
{% endtabs %}
进行跑起服务器,访问http://localhost:8000/posts
路由。你应该可以看到一个博文列表。如果只看到了1条,访问http://localhost:8000/blog
创建多个博文,以便稍后我们要展现单个博文。
显示作者
还记得之前我们建立了作者关联。Orator将会取得该关联为我们生成一个属性,这样我们可以直接将作者的名字也显示出来:
{% tabs %}
{% tab title="resources/templates/posts.html" %}
{% for post in posts %}
{{ post.title }} by {{ post.author.name }}
<br>
{{ post.body }}
<hr>
{% endfor %}
{% endtab %}
{% endtabs %}
让我们来重复该过程,但是稍微做一些修改。
单个博文路由
接下来我们来显示单个博文。我需要为该方法加入路由:
{% tabs %}
{% tab title="routes/web.py" %}
Get('/post/@id', 'PostController@single')
{% endtab %}
{% endtabs %}
注意这里拥有一个@id
字符串。后面我们可以通过此方式从URL中来获取参数。
Single 方法
让我们来创建一个single
方法,用来显示单条博文。
{% tabs %}
{% tab title="app/http/controllers/PostController.py" %}
from app.Post import Post
from masonite.request import Request
from masonite.view import View
...
def single(self, view: View, request: Request):
post = Post.find(request.param('id'))
return view.render('single', {'post': post})
{% endtab %}
{% endtabs %}
我们使用param()
方法来获取URL的参数。注意这里键是由之前路由中指定的@id
{% hint style="info" %}
在真是应用中我可能会命名类似@slug
,以及使用request().param('slug')
来获取.
{% endhint %}
单个博文视图
我只需要显示单个博文,所以就放在一个简单视图中:
{% tabs %}
{% tab title="resources/templates/single.html" %}
{{ post.title }}
<br>
{{ post.body }}
<hr>
{% endtab %}
{% endtabs %}
继续跑起服务,访问http://localhost:8000/post/1
路由以及http://localhost:8000/post/2
看看有什么不一样。
更新和删除博文
到现在,我们花了很长时间来实现之前的逻辑,现在我们加快速度来实现更新和删除博文。我们假设你已经了解我们之前所学,所以我们会快速去实现余下部分,因为接下来的内容都是之前有学习过的知识点。
更新控制器方法
我们在 PostController
新增一个update方法:
{% tabs %}
{% tab title="app/http/controllers/PostController.py" %}
def update(self, view: View, request: Request):
post = Post.find(request.param('id'))
return view.render('update', {'post': post})
def store(self, request: Request):
post = Post.find(request.param('id'))
post.title = request.input('title')
post.body = request.input('body')
post.save()
return 'post updated'
{% endtab %}
{% endtabs %}
由于现在我们所学内容足以让我们快速一下实现两个方法了。一个用来显示修改表单,另一个用来处理更新数据库。
创建视图
$ craft view update
{% tabs %}
{% tab title="resources/templates/update.html" %}
<form action="/post/{{ post.id }}/update" method="POST">
{{ csrf_field }}
<label for="">Title</label>
<input type="text" name="title" value="{{ post.title }}"><br>
<label>Body</label>
<textarea name="body">{{ post.body }}</textarea><br>
<input type="submit" value="Update">
</form>
{% endtab %}
{% endtabs %}
创建路由
记得要为我们创建的两个方法添加路由:
{% tabs %}
{% tab title="routes/web.py" %}
Get('/post/@id/update', 'PostController@update'),
Post('/post/@id/update', 'PostController@store'),
{% endtab %}
{% endtabs %}
就是这样,我们现在应该可以更新我们的博文了。
删除方法
我们继续来扩展它,为它实现删除方法。
{% tabs %}
{% tab title="app/http/controllers/PostController.py" %}
from masonite.request import Request
...
def delete(self, request: Request):
post = Post.find(request.param('id'))
post.delete()
return 'post deleted'
{% endtab %}
{% endtabs %}
添加路由
{% tabs %}
{% tab title="routes/web.py" %}
Get('/post/@id/delete', 'PostController@delete'),
{% endtab %}
{% endtabs %}
注意这里我们使用了GET
路由,这里使用POST
方法会更合理,这里留给你自己实现。我们这里只是在更新视图里加入一个删除链接用来删除博文。
更新模板
我们可以方一个删除链接到我们更新模板:
{% tabs %}
{% tab title="resources/templates/update.html" %}
<form action="/post/{{ post.id }}/update" method="POST">
{{ csrf_field }}
<label for="">Title</label>
<input type="text" name="title" value="{{ post.title }}"><br>
<label>Body</label>
<textarea name="body">{{ post.body }}</textarea><br>
<input type="submit" value="Update">
<a href="/post/{{ post.id }}/delete"> Delete </a>
</form>
{% endtab %}
{% endtabs %}
真棒!你现在应该有个博客,你可以创建,显示,更新以及删除博文!继续去实现惊人的东西吧!