django本身是有一些django-admin命令的,他们都是基于manage.py来使用的。
比如,python3 manage.py createsuperuser xxx
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py dumpdata path.modle --pk num # 将数据库中表名为modle,pk为 unm的数据导出来
python3 manage.py loaddata xxx.json # 将有表数据的json文件导入数据库
等等...
但是,更多时候,我们对于工程的操作,往往是需要自己定义一些命令的,以更方便的操控工程。所以,就有了自定义 django-admin 管理命令一说。
举个简单的例子,fab 是可以用作在远程机器上执行命令的一个工具,有时候,我们会将部署服务的命令集成到 fabfile.py,这样,就可以使用 fab 命令去完成服务的部署了。但是 fab 往往是单独存在的,需要我们在terminal直接输入 fab 命令,这样跟整个工程的粘度,就不是很大了。所以我们会考虑将 fab 集成到 django-admin 里, 通过 python3 manage.py fab task:param remote_machine_address,的这类命令,直接实现服务的部署操作。
对于自定义的 django-admin 的创建,本身并不难:
首先:自定义的Django-admin管理命令本质上是一个python脚本文件,它的存放路径必须遵循一定的规范,一般位于app/management/commands
目录。整个文件夹的布局如下所示:
app01/
__init__.py
models.py
management/
__init__.py
commands/
__init__.py
_private.py # 以下划线开头文件不能用作管理命令
my_commands.py # 这个就是自定义的管理命令脚本,文件名即为命令名
tests.py
views.py
这个布局是不能变的。这里注意,commands 文件夹下可以有多个py文件,基本上每个py文件的文件名,就是自定义管理命令的命令名称。所以,在一个commands里是可以创建出多条django-admin命令的。另外就是commands文件夹中,下划线开头的py文件,是不会被系统认定为django-admin命令的,所以像这种类型的文件都可以用来存储一些方法文件。
django-admin的具体定义:
每一个自定义的管理命令本质是一个Command
类, 它继承了Django的Basecommand
或其子类, 主要通过重写handle()
方法实现自己的业务逻辑代码,而add_arguments()
则用于帮助处理命令行的参数,如果运行命令时不需要额外参数,可以不写这个方法。
from django.core.management.base import BaseCommand
class Command(BaseCommand):
# 帮助文本, 一般备注命令的用途及如何使用。
help = 'Some help texts'
# 处理命令行参数,可选
def add_arguments(self, parser):
pass
# 核心业务逻辑
def handle(self, *args, **options):
pass
这里注意 add_arguments() 这个函数中的参数,parser,它是个特殊的对象,使用方法类似于下面的演示。
from django.core.management.base import BaseCommand
class Command(BaseCommand):
# 帮助文本, 一般备注命令的用途及如何使用。
help = "Print Hello World!"
# 给命令添加一个名为name的参数
def add_arguments(self, parser):
parser.add_argument('name')
# 核心业务逻辑,通过options字典接收name参数值,拼接字符串后输出
def handle(self, *args, **options):
msg = 'Hello World ! '+ options['name']
self.stdout.write(msg) 此时当你再次运行`python manage.py hello_world John`命令时,你将得到如下输出结果:
其实,关于这个参数,还有更多的用法,例如:
import datetime
import logging
import subprocess
import textwrap
from django.core.management.base import BaseCommand
DEFAULT_DAYS = 7
START_DATE = datetime.datetime(2018, 6, 24)
_BUILD_TREE = "/bldmnt/storage61/release"
_TERMINAL_METASTATES = ('failed')
logger = logging.getLogger('webapps')
class Command(BaseCommand):
"""
This command takes 1 argument, buildtype
"""
help = textwrap.dedent(__doc__).strip()
def add_arguments(self, parser):
# Named (optional) arguments
parser.add_argument(
'-b', '--buildtype',
type=str,
choices=('sb', 'ob'),
required=True,
help='Build type either SB or OB',
)
parser.add_argument(
'--days', '-d',
dest="days",
default=DEFAULT_DAYS,
help='Number of days to check storage. Default: %(default)s.',
type=int,
)
def handle(self, *args, **options):
"""
Entry point when management command is executed.
"""
total_size = 0
buildtype = options['buildtype']
INTERVAL = options['days']
END_DATE = START_DATE + datetime.timedelta(days=INTERVAL)
DAYS_INTERVAL = datetime.timedelta(days=1)
current_start = START_DATE
他完全和 python 的 parser 这个模块,用法一致。
关于这个模块的具体参数,及用法可以参考:
python之parser.add_argument()用法——命令行选项、参数和子命令解析器_夏普通的博客-CSDN博客_parser.add_argument(
添加 add_arguments 的方式,在写handle的时候,都是通过,options 取的,是当字典来用的。另外提一种,不添加 add_arguments 方法的方式,直接安位传参。
import os
from django.conf import settings
from django.core.management.base import BaseCommand
class Command(BaseCommand):
def handle(self, *args, **options):
os.system('%s %s' % (settings.FAB_CMD, ' '.join(args)))
def run_from_argv(self, argv):
parser = self.create_parser(argv[0], argv[1])
options = parser.parse_args([])
cmd_options = vars(options)
cmd_options.pop('args', ())
self.execute(*argv[2:], **cmd_options)
def print_help(self, prog_name, subcommand):
os.system('%s --help' % settings.FAB_CMD)
只需要重写 run_from_argv 这个方法,这样会让 opthions 的内容变为空字典。在manage.py 后使用的时候,就直接按位传参就可以了。
另外,再扯点fab命令,首先下载 fabric 包。
然后就是 fabric 的使用,命令:fab --fabfile xxx.py task:param remote_machine_address
fab的使用,是必须有个配置文件的,这个文件放到工程的根目录且名为 fabfile.py 的时候,那么使用fab命令就可以不给 --fabfile 参数,如果是放到其他位置,或是叫其他名称,那就需要给 --fabfile 参数,以明确指定用哪个fab文件。
对fab的一些参数的说明,可以参考:Python Fabric模块详解 - 君无颜 - 博客园
看一个真实的fabfile:
from fabric.api import env, sudo, task, run
from fabric.context_managers import remote_tunnel, cd
from fabric.utils import puts
PROJECT_NAME = 'buildapi'
SERVICE_ROOT_SB = '/service/buildapi-sb'
SERVICE_ROOT_OB = '/service/buildapi-ob'
BUILDAPI_ROOT = '/build/mts/buildapi'
# Settings:
env.roledefs = {
'sjc31': [
],
'wdc': [
],
'stage': [
'
],
}
# Note: task(s) will be executed on hosts in the order listed
env.roledefs['all'] = (env.roledefs['wdc'] + env.roledefs['sjc31'])
set_common_settings()
# Tasks:
all = get_role_setter('all')
sjc31 = get_role_setter('sjc31')
wdc = get_role_setter('wdc')
stage = get_role_setter('stage')
sync = get_sync_task(PROJECT_NAME, BUILDAPI_ROOT)
restart_sb = get_restart_task(PROJECT_NAME, SERVICE_ROOT_SB)
restart_ob = get_restart_task(PROJECT_NAME, SERVICE_ROOT_OB)
@task
def deploy(rev=None, force=False, tunnel=False):
"""Deploy buildapi. Usage: deploy[:rev=REVISION][:force=1][:tunnel=1]"""
paragraph()
puts(hl('Deploying buildapi...'))
if sync(rev):
py38env(tunnel)
restart_sb(force)
restart_ob(force)
else:
puts(warn('No need to restart.'))
@task
def py38env(tunnel=False):
"""Create/upgrade the py38 virtualenv. Usage: py38env[:tunnel=1]"""
if tunnel:
tunnel_port = 10210
with remote_tunnel(remote_port=tunnel_port,
local_host="build-artifactory",
local_port=80):
sudo('/usr/bin/make -C %s -f ./py38env.mk '
'BUILD_ARTIFACTORY=127.0.0.1:%s'
% (BUILDAPI_ROOT, tunnel_port))
else:
sudo('/usr/bin/make -C %s -f ./py38env.mk' % BUILDAPI_ROOT)
deploy_check()
@task
def deploy_check():
"""Deploy test for django admin check command if setup correctly"""
with cd(BUILDAPI_ROOT):
run('./manage_sb.sh check')