当前位置:   article > 正文

三十三、Django进阶:自定义manage.py shell管理命令以及案例(附源码)_python manage.py shell

python manage.py shell

每次在启动Django服务之前,都会在终端运行python manage.py xxx的管理命令。其实还可以自定义管理命令,这对于执行独立的脚本或任务非常有用,比如系统初始化、清除缓存、导出用户邮件清单或发送邮件等等。

自定义的管理命令不仅可以通过manage.py运行,还可以通过Linux或Celery的crontab服务将其设成定时任务。

自定义Django-admin命令一共分三步:创建文件夹布局、编写命令代码和测试使用。

本篇末尾将实现三个自定义管理命令的实际应用案例(系统参数初始化、数据库连接状态、邮件发送)。

自定义shell注意事项

  1. management和commands每个目录下都必须有个__init__.py空文件,表明这是一个python包。另外以下划线开头的文件名不能用作管理命令脚本。
  2. management/commands目录可以位于任何一个app的目录下,Django都能找到它。
  3. 一般建议每个python脚本文件对应一条管理命令。
  4. 当使用管理命令并希望在控制台输出指定信息时,应该使用self.stdoutself.stderr方法,而不能直接使用python的print方法。另外,不需要在消息的末尾加上换行符,它将被自动添加。

创建文件夹布局

自定义的Django-admin管理命令本质上是一个python脚本文件,它的存放路径必须遵循一定的规范,一般位于app/management/commands目录。整个文件夹的布局如下所示:

  1. app/
  2. __init__.py
  3. models.py
  4. management/
  5. __init__.py
  6. commands/
  7. __init__.py
  8. _private.py # 以下划线开头文件不能用作管理命令
  9. init.py # 这个就是自定义的管理命令脚本,文件名即为命令名
  10. tests.py
  11. views.py

注意

  • managementcommands每个目录下都必须有个__init__.py空文件,表明这是一个python包。另外以下划线开头的文件名不能用作管理命令脚本。
  • management/commands目录可以位于任何一个app的目录下,Django都能找到它。
  • 一般建议每个python脚本文件对应一条管理命令。

编写命令代码

定义结构

每一个自定义的管理命令本质是一个Command, 它继承了DjangoBasecommand或其子类, 主要通过重写handle()方法实现自己的业务逻辑代码,而add_arguments()则用于帮助处理命令行的参数,如果运行命令时不需要额外参数,可以不写这个方法。

  1. from django.core.management.base import BaseCommand
  2. class Command(BaseCommand):
  3. # 帮助文本, 用于标注命令的用途及如何使用。
  4. help = 'Some help texts'
  5. # 处理命令行参数,可选
  6. def add_arguments(self, parser):
  7. pass
  8. # 核心业务逻辑
  9. def handle(self, *args, **options):
  10. pass

 定义一个名为show_version的命令。当运行python manage.py show_version命令时,控制台会打印出提示和版本信息。在drf_pro/management/commands目录下新建show_version.py, 添加如下代码:

  1. import sys
  2. from django.core.management.base import BaseCommand
  3. class Command(BaseCommand):
  4. """
  5. 执行指令:python manage.py show_version
  6. 显示帮助信息 python manage.py show_version -h
  7. 输入库名称,显示库的帮助信息 python manage.py show_version sys
  8. """
  9. # 帮助信息 用于备注命令的用途及如何使用
  10. help = f"帮助信息"
  11. # 可选
  12. def add_arguments(self, parser):
  13. pass
  14. # 业务逻辑
  15. def handle(self, *args, **options):
  16. self.stdout.write(f"{self.help},当前系统环境版本:{sys.version}")
  17. self.stdout.write("this is django custom command line")

注意:当使用管理命令并希望在控制台输出指定信息时,应该使用self.stdoutself.stderr方法,而不能直接使用pythonprint方法。另外,不需要在消息的末尾加上换行符,它将被自动添加。

此时当进入项目文件夹运行python manage.py show_version命令时,将得到如下输出结果:

添加参数

通过命令行给show_version命令传递一个name参数,以实现运行python manage.py show_version命令时 打印出对应库的信息。

修改show_version.py, 添加add_arguments方法,该方法的作用是给自定义的handle方法添加1个或多个参数。

  1. import sys
  2. from importlib import import_module
  3. from django.core.management.base import BaseCommand
  4. # class CommandHello(BaseCommand): # error 错误的命名
  5. class Command(BaseCommand):
  6. """
  7. 执行指令:python manage.py show_version
  8. 显示帮助信息 python manage.py show_version -h
  9. 输入库名称,显示库的帮助信息 python manage.py show_version sys
  10. """
  11. # 帮助信息 用于备注命令的用途及如何使用
  12. help = f"当前系统环境版本:{sys.version}"
  13. # 可选
  14. def add_arguments(self, parser):
  15. # 给命令添加一个名为name的参数
  16. parser.add_argument('name')
  17. # 业务逻辑 通过options字典接收name参数值
  18. def handle(self, *args, **options):
  19. self.stdout.write(f"{self.help}")
  20. self.stdout.write("this is django custom command line")
  21. help(import_module(options.get("name")))

再次运行python manage.py show_version os命令时,将得到如下输出结果:

如果直接运行命令而不携带参数,将会报错,如下所示:

案例

案例1:初始化系统参数

定义一个init的系统参数初始化脚本,将静态文件的信息全部录入到数据库中。static/files/sys_config.json的文件内容格式如下:

定义init,py的命令文件,内容如下:

  1. # packages/drf_pro/management/commands/init.py
  2. import os
  3. import json
  4. from django.conf import settings
  5. from django.core.management.base import BaseCommand
  6. from packages.drf_pro.models import Config
  7. class Command(BaseCommand):
  8. help = "系统初始化配置"
  9. tags = "[init conf]"
  10. def add_arguments(self, parser):
  11. pass
  12. def handle(self, *args, **options):
  13. self.stdout.write(f"{self.tags} start initializer config tbale...")
  14. conf_file = os.path.join(settings.BASE_DIR, "static", "files", "sys_config.json").replace("\\","/")
  15. if os.path.exists(conf_file):
  16. with open(conf_file,encoding="utf-8") as conf_f:
  17. configs = json.loads(conf_f.read())
  18. try:
  19. for conf in configs:
  20. is_exists = Config.objects.filter(name_en=conf["name_en"])
  21. if is_exists:
  22. conf_item = is_exists.first()
  23. conf_item.name_zh = conf.get("name_zh", "")
  24. conf_item.remark = conf.get("remark", "")
  25. conf_item.conf_type = conf.get("conf_type", "")
  26. conf_item.save()
  27. else:
  28. Config.objects.create(**conf)
  29. self.stdout.write(f"{self.tags} sync over.....")
  30. except Exception as e:
  31. self.stderr.write(f"{self.tags} sync error, info:{e}")
  32. else:
  33. self.stderr.write(f"not exists:{conf_file}")

输出

PGSQL中查看初始结果

案例2:检查数据库连接是否正常

无论使用常规方式还是Docker在生产环境中部署Django项目,需要确保数据库连接已就绪后才进行数据库迁移(migrate)的命令(Docker-composedepends选项并不能确保这点),否则Django应用程序会出现报错。

这时可以自定义一个check_db_server的命令,如下所示:

  1. # packages/drf_pro/management/commands/check_db_server.py
  2. import time
  3. from django.db import connections
  4. from django.db.utils import OperationalError
  5. from django.core.management import BaseCommand
  6. class Command(BaseCommand):
  7. help = "[Check] Run data migrations until db is available"
  8. def handle(self, *args, **options):
  9. self.stdout.write("Waiting for database...")
  10. db_conn = None
  11. while not db_conn:
  12. try:
  13. db_conn = connections['default']
  14. except OperationalError:
  15. self.stdout.write('Database unavailable,waiting 1 second...')
  16. time.sleep(1)
  17. self.stdout.write(self.style.SUCCESS('Database available!'))

定义好这个命令后每次在运行python manage.py migrate命令前先运行python manage.py check_db_server即可。

案例3:周期性发送邮件

希望知道每天有多少新用户已注册,这时可以自定义一条send_email的管理命令,将每天新注册用户数量以邮件形式发给自己,如下所示:

  1. # packages/drf_pro/management/commands/send_email.py
  2. from datetime import timedelta
  3. from django.core.mail import mail_admins
  4. from django.utils import timezone
  5. from django.contrib.auth import get_user_model
  6. from django.core.management.base import BaseCommand
  7. User = get_user_model()
  8. class Command(BaseCommand):
  9. help = 'Send Email Information to Admin....'
  10. def handle(self, *args, **options):
  11. today = timezone.now()
  12. yesterday = today - timedelta(1)
  13. user_count = User.objects.filter(date_joined__range=(yesterday, today)).count()
  14. if user_count:
  15. message = "You have got {} user(s) in the past 24 hours".format(user_count)
  16. subject = (f"New user count for {today.strftime('%Y-%m-%d')}: {user_count}")
  17. try:
  18. mail_admins(subject=subject, message=message, html_message=None)
  19. except Exception as e:
  20. self.stderr.write(f"Send email error, msg:{e}")
  21. self.stdout.write(f'Send email success, user count:{user_count}')
  22. else:
  23. self.stdout.write('No new users today')

如果在终端运行python manage.py send_email命令,将得到如下输出结果:

注意:真正发送邮件成功需要设置Email后台及管理员,测试环境下可以使用如下简单配置:

  1. EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
  2. DEFAULT_FROM_EMAIL = "rocket_2014@126.com"
  3. ADMINS = [("steverocket", "rocket_2014@126.com"), ]
'
运行

但是如果每天都要进入终端运行这个命令实在太麻烦了,完全可以使用Linuxcrontab服务或Celery-Beat将其设成周期性定时任务task,这时只需要调用Djangocall_command方法即可。

  1. # drf_pro/tasks.py, 可以任一app目录下新建task
  2. from celery import shared_task
  3. from django.core.management import call_command
  4. # 将django自定义的指令放入到定时任务中
  5. @shared_task
  6. def send_email():
  7. call_command("send_email", )

代码示例:https://download.csdn.net/download/zhouruifu2015/87611608https://download.csdn.net/download/zhouruifu2015/87611608

输入才有输出,吸收才能吐纳。——码字不易


更多资料 · 微信公众号搜索【CTO Plus】关注后,获取更多,我们一起学习交流。

关于公众号的描述访问如下链接


关于Articulate“做一个知识和技术的搬运工。做一个终身学习的爱好者。做一个有深度和广度的技术圈。”一直以来都想把专业领域的技https://mp.weixin.qq.com/s/0yqGBPbOI6QxHqK17WxU8Q 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/887682
推荐阅读
相关标签
  

闽ICP备14008679号