django實現自定義manage命令的擴充套件

迎風而來發表於2019-08-12

在Django開發過程中我們都用過django-admin.py和manage.py命令。

django-admin.py是一個命令列工具,可以執行一些管理任務,比如建立Django專案。而manage.py是在建立每個Django project時自動新增在專案目錄下的,只是對manage.py的一個簡單包裝,其功能是將Django project放到sys.path目錄中,同時設定DJANGO_SETTINGS_MODULE環境變數為當前project的setting.py檔案。

Django 對於命令的新增有自己的一套規範,我們可以為每個app 指定命令。簡單來書就是我們在使用manage.py檔案執行命令的時候,可以自定製自己的命令,來實現命令的擴充。

對於自定義Command我們從兩方面介紹一是內部執行原理,二是如何實行自定義Command

一、內部原理實現

django-admin.py呼叫django.core.management來執行命令:

建立django專案會自動生成manage.py檔案:

import os
import sys


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gaoyou.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

excute_from_command_line()函式會根據命令列引數解析出命令的名稱,根據命令名稱呼叫相應的Command執行命令。Command位於各個管理模組的commands模組下面。

commands的建立方法:

1、在app內建立一個名字為:management資料夾(在你自己指定的應用下建立即可)
2、在management資料夾裡面建立名為:commands的資料夾
3、在commands資料夾下建立名為:任意py檔案(啟動的時候就是根據該檔名進行啟動的,注意:commands目錄內都包含__init__.py檔案)

此時py檔名就是你的自定製命令,我們可以使用下面方式進行執行

python manage.py 命令名(即任意py檔名不用加.py)
#類似我們遷移資料庫命令
#python manage.py makemigrations
#python manage.py migrate

所謂管理模組,是指在app模組下的名字為management的模組。Django通過django.core.management.find_management_module函式發現"管理模組":

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檔案的名稱匹配到命令名稱:

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類:

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的直接或間接子類。

二、自定義應用

Django的Command命令是要放到我們建立app下的management/commands目錄下的(需自己手動建立該檔案目錄)。

注意:請確保management/commands目錄下包含__init__.py檔案

首先對於檔名可以自行定義沒有要求,內部需要定義一個Command類並繼承BaseCommand類或其子類。

  1. 它必須定義一個Command類並擴充套件自BaseCommand或其 子類。
  2. 其中help是command功能作用簡介,handle函式是主處理程式,add_arguments函式是用來接收可選引數的(如果沒有引數該方法可以不寫)

我們通過在輸入命令後再控制檯輸出一個hello world為例:

task.py

from django.core.management.base import BaseCommand, CommandError
from django.db import models

class Command(BaseCommand):
    help = '每日凌晨對當天資料庫進行更新'

    def handle(self, *args, **options):
        print('hello world')

在Terminal控制檯將目錄切換到你建立的Django專案目錄下執行:python manage.py task

執行後即可在控制檯看到輸出hello world 說明自定義Commond成功!!!

如果在輸入命令想要輸出引數怎麼辦呢?例如:python mange.py task 引數

task.py

from django.core.management.base import BaseCommand, CommandError
from django.db import models


class Command(BaseCommand):
    help = '每日凌晨對當天資料庫進行更新'

    # 接收引數
    def add_arguments(self, parser):
        parser.add_argument('offset', type=int, help='天數轉移量')

    def handle(self, *args, **options):
        offset = options['offset']  # 拿到引數的值
        print(offset)
        print('hello world')
        self.stdout.write(self.style.SUCCESS('{} Successfully {}'.format('接收成功', offset)))  #可以自定製在控制檯輸出的內容

在Terminal控制檯將目錄切換到你建立的Django專案目錄下執行:python manage.py task  1314

 

 

相關文章