Day 13 实作 manage.py

前言

前天我们写好了 create_app,但是还没有人呼叫他,今天我们就要来呼叫他 (当然还不能用啦,因为蓝图都没写好)。

这个档案叫做 manage.py,顾名思义,他就是用来管理的,我们可以用它来操作这个 application 及其附属的资源,像是资料库等等。而他是会用 flask 这个指令来操作 (跟 flask run 同一个),我们预计会加入以下几个指令:

  • init_db 会把资料库初始化,但如果本来就存在他不会乱动。
  • reset_db 会把资料库整个砍掉再重建。
  • create_user 会用来新增一个使用者,当然也可以透过设定 is_admin=True 来让它变成管理员。

manage.py

事不宜迟,我们就开始吧。但在开始之前我们要先写一下 manage.py 的基本架构。後面我们会执行他,所以也会连带执行到之前的 create_app,如果想看他正常执行的话,要先把那几行蓝图的程序码注解掉,不然会跑错误,毕竟我们根本没定义过他们。


from os import getenv
from app import create_app, db

app = create_app(getenv("FLASK_ENV"))


@app.shell_context_processor
def make_shell_context():
    return globals()

我们在最一开始引入了 getenv,这是之前很熟悉的函式了。接着还有 create_appdb,後者在今天不会用到,只是先引入留着,之後会用到。

接着我们用 create_app 造出了一个 app,使用的参数是环境变数 FLASK_ENV,所以自己要先设定好这个环境变数。

最後三行是 flask 提供的一个可以方便 debug 的小工具,他可以执行 flask 然後开一个 shell 让你跟他互动,可以查现在 app 里面有什麽内容之类的。使用的方式也很简单,用 flask shell 这个指令即可,他就会开一个 shell 给你用。但这时候使用他会喷错误,我们等等还需要设定一个环境变数来处理。要使用这个 shell 是不需要这三行的,flask 本来就可以直接用,他们的用意是把现在的环境直接复制到 shell 里面,让 shell 可以看的资讯更多 (整个 globals()),他这边用到了 context 这个词,如果深入钻研的话会一直看到他,但在此处就先不提。可以试试看不加这三行,可以很明显地发现如果不加然後在 shell 使用 create_app 会出现未定义。

这时候可能会想,我们根本没有 app.run() 或是类似的函式,那他要怎麽执行?我们需要先设定 FLASK_APP 这个环境变数,它是用来告知 flask 我们的 app 在哪里的。以此处为例,我们需要把它设为 manage.py,这样 flask 就会知道我们的 app 就放在 manage.py 里面,然後他就会自动自发地执行他。设定完成之後,flask shell 应该就可以使用了,可以尝试看看。

但我们当然不是要执行 shell,我们需要让他真的跑起来让大家都可以用,所以我们就需要用之前用过的 flask run 来让他跑起来,如同上面所述,它会自动去抓 app 然後让他跑起来。

自订指令

因为这些新指令都是有关资料库的,所以在加入新的指令到 manage.py 之前,我们必须先写好资料库端的函式,基本上就是不要让 db 离开 app/database。我们需要先多开一个 helper.py 放在 app/database,然後加入一些函式给外部引入。

from .models import db, Users


def init():
    db.create_all()


def reset():
    db.drop_all()
    db.create_all()


def add_user(username, password, email, introduction=None, is_admin=False):
    user = Users(username, password, email, introduction, is_admin)
    try:
        db.session.add(user)
        db.session.commit()
        return True
    except:
        return False

这边加入了三个函式,我们一个一个看。记得也要在 app/database/__init__.py 加入 from .helper import init, reset, add_user

  • init 是用来初始化资料库的,我们使用 create_all 这个函式来帮忙,他会帮我们把资料库的 table 都建好,但如果已经存在的话,他不会把资料删掉。
  • reset 是用来把资料删光光并重新建好资料库的,我们使用 drop_allcreate_all,分别会把资料都删光及建立资料库,所以把他们两个一起用就可以达到重设的目的了。
  • add_user 是用来新增使用者的函式,要建立一个使用者很简单,就只要用我们给的参数建立一个 Users 物件即可 (最前面引入了),接下来我们用 try ... except ... 来避免错误,没错误就回传 True,反之 False,当然可以做得更细致 (判断使用者名称是否重复等等),但这边就展示性质,不处理其他状况。这边我们使用 db.session.add 来把这个使用者加入到 session 里面,接着再 db.session.commit 把这个变化写入资料库。

结束资料库端的函式之後,我们可以看看 manage.py 要怎麽接。要记得最前面需要引入 from app.database import init, reset, add_user

@app.cli.command(name="init_db")
def init_db():
    init()

@app.cli.command(name="reset_db")
def reset_db():
    reset()

@app.cli.command(name="create_user")
def create_user():
    username = input("Username: ")
    password = input("Password: ")
    email = input("Email: ")
    is_admin = True if input("Is admin or not (y or n): ") == "y" else False
    if add_user(username, password, email, None, is_admin):
        print("OK")
    else:
        print("Failed")

这里我们使用了 app.cli.command 这个装饰器,flask 的这个部分是用 click 实作的,所以会有一些点跟他一样,像是函式名称是 create_user 但要呼叫的时候需要用 create-user,我自己是习惯把它改掉,使用 name 这个参数。

init_db 的部分基本上就是刚刚的 init,不管他。reset 也跟刚刚一样,不管他。create_user 我们用 input 的方式让人输入资料,然後加入。这里可以自己加入 email 的判断、密码长度是否过短之类的侦测,这边就不大费周章了。

把他们加入完之後,就可以直接用 flask 来执行,我们可以先 flask init_db 让他初始化资料库,然後 flask add_user 新增一个使用者,没有意外的话应该都可以跑得很顺利。

References

Command Line Interface
Session Basics


<<:  Day13 测试写起乃 - controller test

>>:  [Day 1] 全民疯AI系列2.0-机器学习实战手册

在Windows 10中启用或禁用Windows复原环境(WinRE)

Windows复原环境(WinRE)可用於解决Windows 10作业系统不可启动的常见问题。借助W...

JS 陈述式 v.s 表达式 DAY51

陈述式 v.s 表达式 陈述式 JS 的语句类型,用於命令执行指定的一系列操作 最大特徵是不会回传结...

【面试】coding interview

另一系列悲剧..不小心按到上一页.. 感觉这篇还少了点什麽? 如果平常只用过 Leetcode,建...

Day 02-是在 Hello?什麽都要 Hello 一下之 Hello Terraform

软功就是什麽都要 Hello 一下之 Hello terraform 这张就会开始动手做了,还没设定...

轻松跨越Windows地雷而不会被炸得粉身碎骨

曾经被系统的地雷,炸得支离破碎 很多好用的应用程序都对Windows不太友善,今天就让你轻松跨越这些...