扩展Django实现自己的manage命令

django-admin.py是一个命令行工具,可以执行一些管理任务,比如创建Django项目。

1
2
django-admin.py startproject demo_project
python manage.py runserver 0.0.0.0:8000

0.0.0.0 让其它电脑可连接到开发服务器,8000 为端口号。如果不说明,那么端口号默认为 8000。

在浏览器输入你服务器的 ip(这里我们输入本机 IP 地址: 127.0.0.1:8000) 及端口号,就可以启动了。

manage.py是在创建每个Django project时自动添加在项目目录下的,只是对django-admin.py的一个简单包装,相较于django-admin.py,其额外的功能是将Django project放到sys.path目录中,同时设置DJANGO_SETTINGS_MODULE环境变量为当前project的setting.py文件。

django-admin.py调用django.core.management来执行命令:

1
2
3
4
5
#!/usr/bin/env python
from django.core import management

if __name__ == "__main__":
management.execute_from_command_line()

excute_from_command_line()函数会根据命令行参数解析出命令的名称,根据命令名称调用相应的Command执行命令。Command位于各个管理模块的commands模块下面。

所谓管理模块,是指在app模块下的名字为management的模块。Django通过django.core.management.find_management_module函数发现”管理模块”:

1
2
3
4
5
6
7
8
9
10
11
12
13
# django.core.management.find_management_module()
def find_management_module(app_name):
"""
Determines the path to the management module for the given app_name,
without actually importing the application or the management module.

Raises ImportError if the management module cannot be found for any reason.
"""
parts = app_name.split('.')
parts.append('management')
parts.reverse()
part = parts.pop()
path = None

然后通过django.core.management.find_commands函数找到命令类。find_commands函数会在管理模块下查找.py文件,并将.py文件的名称匹配到命令名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
def find_commands(management_dir):
"""
Given a path to a management directory, returns a list of all the command
names that are available.

Returns an empty list if no commands are defined.
"""
command_dir = os.path.join(management_dir, 'commands')
try:
return [f[:-3] for f in os.listdir(command_dir)
if not f.startswith('_') and f.endswith('.py')]
except OSError:
return []

最后,通过django.core.management.load_command_class函数加载该.py文件中的Command类:

1
2
3
4
5
6
7
8
def load_command_class(app_name, name):
"""
Given a command name and an application name, returns the Command
class instance. All errors raised by the import process
(ImportError, AttributeError) are allowed to propagate.
"""
module = import_module('%s.management.commands.%s' % (app_name, name))
return module.Command()

在执行命令的时候,会执行相应Command类的handle方法。所有的Command类都应该是django.core.management.base.BaseCommand的直接或间接子类。

原理搞清楚了,扩展manage命令就很容易了。创建一个app并加入到settings的INSTALLED_APPS中,在该app下面创建management.commands模块,并创建hello.py文件:

1
2
3
4
5
6
7
from django.core.management.base import BaseCommand, CommandError
from django.db import models
import os

class Command(BaseCommand):
def handle(self, *args, **options):
print 'hello, django!'

就可以使用hello命令了:

1
2
$ python manage.py hello
hello, django!

实际应用

通过命令行刷新缓存:

1
2
3
4
5
6
7
8
9
10
11
12
# cache_clear.py
from django.core.management.base import BaseCommand
from core.cache import cache


class Command(BaseCommand):

def handle(self, **options):
try:
cache.clear()
except Exception:
logger.error(msg=traceback.format_exc())

还可以即时刷新权限,即时执行异步任务等等等,换言之,预留了命令行接口而不是视图接口。

注意点

如果manage命令分散在多个app中,且文件名称一致,则在执行命令的时候会按照settings.py中的INSTALLED_APPS顺序 取第一个匹配的命令。

1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app02.apps.App02Config',
'app01.apps.App01Config',
]

如果app01和app02下都有commands,且文件名称都是hello,则执行python manage.py hello时,执行的时app02中的函数,app01中的不会被执行。

为了避免这种问题,所以一般统一放到core中。

如果觉得写的还行,赞助瓶脉动~
0%