Django 添加自定義命令

使用Django開發(fā),對 python manage.py *** 命令模式肯定不會陌生。比較常用的有 runservermigrate等!

有時(shí)候會有這樣的需求,為 Django 執(zhí)行一些定時(shí)任務(wù),比如通知搜索引擎,例如百度,提交網(wǎng)站的一些地址給他們,則可以通過為 Djangomanage.py 添加自定義命令可以很容易的解決這個(gè)問題。

所以我們就來講講如何自定義擴(kuò)展manage命令。

源碼分析

manage.py 文件是通過 django-admin startproject project_name 生成的。

  1. manage.py的源碼

    • 首先設(shè)置了 settings 文件

    • 其次執(zhí)行了一個(gè)函數(shù)django.core.management.execute_from_command_line(sys.argv),這個(gè)函數(shù)傳入了命令行參數(shù) sys.argv

      #!/usr/bin/env python
      import os
      import sys
          
      if __name__ == "__main__":
          os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CIServer.settings")
          try:
              from django.core.management import execute_from_command_line
          except ImportError:
              raise ImportError(
                  "Couldn't import Django. Are you sure it's installed and available "
                  "on your PATH environment variable? Did you forget to activate a "
                  "virtual environment?"
              )
          execute_from_command_line(sys.argv)
      
  2. execute_from_command_line

    里面調(diào)用了ManagementUtility類中的execute方法

    def execute_from_command_line(argv=None):
        """
        A simple method that runs a ManagementUtility.
        """
        utility = ManagementUtility(argv)
        utility.execute()
    

    execute 中主要是解析了傳入的參數(shù) sys.argv ,并且調(diào)用了get_command()

  3. get_command

    def get_commands():
        """
        Returns a dictionary mapping command names to their callback applications.
    
        This works by looking for a management.commands package in django.core, and
        in each installed application -- if a commands package exists, all commands
        in that package are registered.
    
        Core commands are always included. If a settings module has been
        specified, user-defined commands will also be included.
    
        The dictionary is in the format {command_name: app_name}. Key-value
        pairs from this dictionary can then be used in calls to
        load_command_class(app_name, command_name)
    
        If a specific version of a command must be loaded (e.g., with the
        startapp command), the instantiated module can be placed in the
        dictionary in place of the application name.
    
        The dictionary is cached on the first call and reused on subsequent
        calls.
        """
        commands = {name: 'django.core' for name in find_commands(upath(__path__[0]))}
    
        if not settings.configured:
            return commands
    
        for app_config in reversed(list(apps.get_app_configs())):
            path = os.path.join(app_config.path, 'management')
            commands.update({name: app_config.name for name in find_commands(path)})
    
        return commands
    

    get_command 里遍歷所有注冊的 INSTALLED_APPS 路徑下的management 尋找 (find_commands) 用戶自定義的命令。

    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')
        # Workaround for a Python 3.2 bug with pkgutil.iter_modules
        sys.path_importer_cache.pop(command_dir, None)
        return [name for _, name, is_pkg in pkgutil.iter_modules([npath(command_dir)])
                if not is_pkg and not name.startswith('_')]
    

    可以發(fā)現(xiàn)并注冊的命令是commands目錄下不以"_"開頭的文件名。

  4. load_command_class

    將命令文件***.py中的Command類加載進(jìn)去。

    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()
    
  5. Command

    Command 類要繼承 BaseCommand 類,其中很多方法,一定要實(shí)現(xiàn)的是 handle 方法,handle 方法是命令實(shí)際執(zhí)行的代碼。

具體實(shí)現(xiàn)

根據(jù)上面說的原理,我們只需要在創(chuàng)建好的應(yīng)用的根目錄創(chuàng)建文件夾名為 management 的目錄,然后繼續(xù)在該目錄創(chuàng)建 commands 的目錄,并在兩個(gè)目錄中都要?jiǎng)?chuàng)建__init__.py 的 python 文件。 目錄創(chuàng)建好之后繼續(xù)在commands 的目錄中添加 ping_baidu.py 文件,文件名將會是 manage.py 的命令名. 目錄結(jié)構(gòu)如下:

(python3) ?  blog tree   
.
├── __init__.py
└── management
    ├── __init__.py
    └── commands
        ├── __init__.py 
        └── ping_baidu.py

ping_baidu.py 中實(shí)現(xiàn)命令的具體內(nèi)容

from django.core.management.base import BaseCommand, CommandError
from blog.models import Article, Tag, Category
from DjangoBlog.spider_notify import sipder_notify
from django.contrib.sites.models import Site

site = Site.objects.get_current().domain


class Command(BaseCommand):
    help = 'notify baidu url'

    def add_arguments(self, parser):
        parser.add_argument('data_type', type=str, choices=['all', 'article', 'tag', 'category'],
                            help='article : all article,tag : all tag,category: all category,all: All of these')

    def get_full_url(self, path):
        url = "https://{site}{path}".format(site=site, path=path)
        return url

    def handle(self, *args, **options):
        type = options['data_type']
        self.stdout.write('start get %s' % type)
        notify = sipder_notify()
        urls = []
        if type == 'article' or type == 'all':
            for article in Article.objects.filter(status='p'):
                urls.append(article.get_full_url())
        if type == 'tag' or type == 'all':
            for tag in Tag.objects.all():
                url = tag.get_absolute_url()
                urls.append(self.get_full_url(url))
        if type == 'category' or type == 'all':
            for category in Category.objects.all():
                url = category.get_absolute_url()
                urls.append(self.get_full_url(url))

        self.stdout.write(self.style.SUCCESS('start notify %d urls' % len(urls)))
        notify.baidu_notify(urls)
        self.stdout.write(self.style.SUCCESS('finish notify'))

sipder_notify.py 也很簡單:

from django.contrib.sitemaps import ping_google
import requests
from django.conf import settings


class SpiderNotify():
    //提交百度統(tǒng)計(jì)
    @staticmethod
    def baidu_notify(urls):
        try:
            data = '\n'.join(urls)
            result = requests.post(settings.BAIDU_NOTIFY_URL, data=data)
            print(result.text)
        except Exception as e:
            print(e)
    //熊掌號接入
    @staticmethod
    def baidu_bear_notify(urls):
        try:
            data = '\n'.join(urls)
            result = requests.post(settings.BAIDU_BEAR_NOTIFY_URL, data=data)
            print(result.text)
        except Exception as e:
            print(e)
    //提交到谷歌
    @staticmethod
    def __google_notify():
        try:
            ping_google('/sitemap.xml')
        except Exception as e:
            print(e)

    @staticmethod
    def notify(url):

        SpiderNotify.baidu_notify(url)
        SpiderNotify.__google_notify()
        SpiderNotify.baidu_bear_notify(url)    

至此,基本都完成了,可以終端執(zhí)行./manage.py查看輸出:

(python3) ?  DjangoBlog ./manage.py 

Type 'manage.py help <subcommand>' for help on a specific subcommand.

Available subcommands:

[auth]
    changepassword
    createsuperuser

[blog]
    ping_baidu

可以看到 ping_baidu 命令已經(jīng)出現(xiàn)了,./manage.py ping_baidu --help 可以查看幫助:

(python3) ?  DjangoBlog ./manage.py ping_baidu --help
usage: manage.py ping_baidu [-h] [--version] [-v {0,1,2,3}]
                            [--settings SETTINGS] [--pythonpath PYTHONPATH]
                            [--traceback] [--no-color]
                            {all,article,tag,category}

notify baidu url

positional arguments:
  {all,article,tag,category}
                        article : all article,tag : all tag,category: all
                        category,all: All of these

optional arguments:
  -h, --help            show this help message and exit
  --version             show program's version number and exit
  -v {0,1,2,3}, --verbosity {0,1,2,3}
                        Verbosity level; 0=minimal output, 1=normal output,
                        2=verbose output, 3=very verbose output
  --settings SETTINGS   The Python path to a settings module, e.g.
                        "myproject.settings.main". If this isn't provided, the
                        DJANGO_SETTINGS_MODULE environment variable will be
                        used.
  --pythonpath PYTHONPATH
                        A directory to add to the Python path, e.g.
                        "/home/djangoprojects/myproject".
  --traceback           Raise on CommandError exceptions
  --no-color            Don't colorize the command output.

最后在終端執(zhí)行: ./manage.py ping_baidu all 即可。

此文章同時(shí)同步到我的個(gè)人博客緣來來來 ? Django 添加自定義命令](https://www.fkomm.cn/article/2018/10/17/55.html)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容