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')