1、以 django-admin startproject xxx 为例
1、django-admin实际上在django下载的时候,是作为一个.exe的可执行文件,并被配置进了环境变量,因此可以直接使用django-admin执行命令,创建项目
2、django版本在2.2的时候,在django的bin目录下存在一个django-admin.py的文件,内部代码如下
3、当前django4.0已经取消了bin目录,但是实际执行的代码逻辑是一样的,均是执行了 execute_from_command_line() 这个方法 ,此处新建一个tmp文件夹并 cd 切换进目录
2、python manage.py xxx
1、manage.py 为django项目创建成功后自动生成,其中代码如下,也是执行了execute_from_command_line() 这个方法
3、小结
综上,不管是django-admin还是python manage.py ,最终都会execute_from_command_line() 这个方法
4、execute_from_command_line()
1、PyCharm点击该函数实际显示如下,该函数做了两件事
1、实例化
2、执行
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
utility = ManagementUtility(argv) # 1. 实例化
utility.execute() # 2. 执行
4.1、实例化
1、实例化比较简单,主要体现在绑定 self 的命令行参数
class ManagementUtility:
"""
Encapsulate the logic of the django-admin and manage.py utilities.
"""
def __init__(self, argv=None):
self.argv = argv or sys.argv[:] # 设置命令行参数
self.prog_name = os.path.basename(self.argv[0])
if self.prog_name == '__main__.py':
self.prog_name = 'python -m django'
self.settings_exception = None
4.2、execute() 函数
1、主要关注 execute 函数
2、只要关注中文注释部分即可
3、原先的英文注释已被删除
4、当前流程走完之后最后会走fetch_command()这个函数
def execute(self):
try:
# 1、
# 如果当前的命令为 python manage.py startapp app_xx
# 那么此处的 subcommand 就是 startapp
subcommand = self.argv[1]
except IndexError:
subcommand = 'help' # Display help if no arguments were given.
parser = CommandParser(
prog=self.prog_name,
usage='%(prog)s subcommand [options] [args]',
add_help=False,
allow_abbrev=False,
)
parser.add_argument('--settings')
parser.add_argument('--pythonpath')
parser.add_argument('args', nargs='*') # catch-all
try:
# 2、拿到其他的参数 比如
# 当命令为 python manage.py startapp app_xx
# options: Namespace(settings=None, pythonpath=None, args=['app_xx'])
# args: []
# 当命令为 python manage.py startapp --app_xx
# options: Namespace(settings=None, pythonpath=None, args=[])
# args: ['--app_xx']
options, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
except CommandError:
pass # Ignore any option errors at this point.
try:
settings.INSTALLED_APPS
except ImproperlyConfigured as exc:
self.settings_exception = exc
except ImportError as exc:
self.settings_exception = exc
if settings.configured:
if subcommand == 'runserver' and '--noreload' not in self.argv:
try:
autoreload.check_errors(django.setup)()
except Exception:
apps.all_models = defaultdict(dict)
apps.app_configs = {}
apps.apps_ready = apps.models_ready = apps.ready = True
_parser = self.fetch_command('runserver').create_parser('django', 'runserver')
_options, _args = _parser.parse_known_args(self.argv[2:])
for _arg in _args:
self.argv.remove(_arg)
else:
django.setup()
self.autocomplete()
if subcommand == 'help':
# 3、python manage.py help --command
if '--commands' in args:
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
# 4、python manage.py help
elif not options.args:
sys.stdout.write(self.main_help_text() + '\n')
# 5、python manage.py help makemigrations<除了 --commands 的其他二级命令>
else:
self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
# 6、python manage.py version
elif subcommand == 'version' or self.argv[1:] == ['--version']:
sys.stdout.write(django.get_version() + '\n')
# 7、python manage.py --help 或者 python manage.py -h
elif self.argv[1:] in (['--help'], ['-h']):
sys.stdout.write(self.main_help_text() + '\n')
else:
# 8.除了 help、version
# 比如 python manage.py migrate, subcommand: migrate
self.fetch_command(subcommand).run_from_argv(self.argv)
5、fetch_command()
1、上述流程完成之后,代码会走到当前函数
2、注意,进入到当前函数,会先执行 get_commonds() 函数,这很重要
def fetch_command(self, subcommand):
# 1、 先执行 get_commands(),这很重要
commands = get_commands()
try:
app_name = commands[subcommand]
except KeyError:
if os.environ.get('DJANGO_SETTINGS_MODULE'):
settings.INSTALLED_APPS
elif not settings.configured:
sys.stderr.write("No Django settings specified.\n")
possible_matches = get_close_matches(subcommand, commands)
sys.stderr.write('Unknown command: %r' % subcommand)
if possible_matches:
sys.stderr.write('. Did you mean %s?' % possible_matches[0])
sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
sys.exit(1)
if isinstance(app_name, BaseCommand):
klass = app_name
else:
klass = load_command_class(app_name, subcommand)
return klass
5.1.get_commands()
1、当走进 get_commonds() 的时候,又出现一个 find_commonds(),看看他返回了什么
@functools.lru_cache(maxsize=None)
def get_commands():
# __path__: 存在于__init__文件中,被导入时,返回一个列表,列表的第一个元素是当前文件(当前包)所在的目录的绝对路径
# __path__[0]: 此处为 'D:\\pys\\django_test\\django\\core\\management'
commands = {name: 'django.core' for name in find_commands(__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
2、find_commands()
该函数返回commands目录下的文件名列表
['check', 'compilemessages', 'createcachetable', 'dbshell', 'diffsettings', 'dumpdata', 'flush', 'inspectdb', 'loaddata', 'makemessages', 'makemigrations', 'migrate', 'runserver', 'sendtestemail', 'shell', 'showmigrations', 'sqlflush', 'sqlmigrate', 'sqlsequencereset', 'squashmigrations', 'startapp', 'startproject', 'test', 'testserver']
3、再次回到 get_commonds() 函数
commands根据上一步返回的文件名列表组装,最后返回值均为为 django.core 的字典
@functools.lru_cache(maxsize=None)
def get_commands():
# 此时的 commands 是这样的
# {'check': 'django.core',
# 'compilemessages': 'django.core',
# 'createcachetable': 'django.core',
# 'dbshell': 'django.core',
# ...
commands = {name: 'django.core' for name in find_commands(__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
5.2.返回到 get_commands() 函数
def fetch_command(self, subcommand):
# 1、 先执行 get_commands(),这很重要
# 2、最后 commands 是一个字典 {'makemigrations': 'django.core','migrate': 'django.core',...
commands = get_commands()
try:
# 3、一个合法的命令,此处的 app_name 就是 "django.core"
app_name = commands[subcommand]
except KeyError:
if os.environ.get('DJANGO_SETTINGS_MODULE'):
settings.INSTALLED_APPS
elif not settings.configured:
sys.stderr.write("No Django settings specified.\n")
possible_matches = get_close_matches(subcommand, commands)
sys.stderr.write('Unknown command: %r' % subcommand)
if possible_matches:
sys.stderr.write('. Did you mean %s?' % possible_matches[0])
sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
sys.exit(1)
if isinstance(app_name, BaseCommand):
klass = app_name
# 4、当前的 app_name 是一个字符串对象,因此会走这里
else:
# app_name: django.core
# subcommand: migrate
klass = load_command_class(app_name, subcommand)
return klass
5.3.load_command_class()函数
1、上述最后会走到load_command_class(),传入 django.core 和 migrate # 示例命令
def load_command_class(app_name, name):
# app_name: django.core
# subcommand: migrate
# module = django.management.commands.migrate
module = import_module('%s.management.commands.%s' % (app_name, name))
# 最后返回的是 migrate.py 中的 Command 对象
return module.Command()
5.4.执行完毕
1、执行完 load_command_class() ,整个 fetch_command() 执行结束,返回一个 Command 对象
6、excute() 最后一步
1、4.2的时候已经执行到 下方
...
self.fetch_command(subcommand).run_from_argv(self.argv)
2、fetch_command() 返回的是一个 Command 对象
3、Command对象位于django.core.management.commands目录下
4、此处还是找 migrate.py<示例> 下的 Command类,并找到其父类的 run_from_argv() 方法
5、其中最终执行的就是里面的 excute 方法
def run_from_argv(self, argv):
self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1])
options = parser.parse_args(argv[2:])
cmd_options = vars(options)
args = cmd_options.pop('args', ())
handle_default_options(options)
try:
# 其中最核心的就是这个 execute() 方法,最终执行的也是这个方法
self.execute(*args, **cmd_options)
except CommandError as e:
if options.traceback:
raise
if isinstance(e, SystemCheckError):
self.stderr.write(str(e), lambda x: x)
else:
self.stderr.write('%s: %s' % (e.__class__.__name__, e))
sys.exit(e.returncode)
finally:
try:
connections.close_all()
except ImproperlyConfigured:
pass
6、当点击excute()方法,里面最核心的是 handle 方法
def execute(self, *args, **options):
# 处理一些公共的逻辑
if options['force_color'] and options['no_color']:
raise CommandError("The --no-color and --force-color options can't be used together.")
if options['force_color']:
self.style = color_style(force_color=True)
elif options['no_color']:
self.style = no_style()
self.stderr.style_func = None
if options.get('stdout'):
self.stdout = OutputWrapper(options['stdout'])
if options.get('stderr'):
self.stderr = OutputWrapper(options['stderr'])
if self.requires_system_checks and not options['skip_checks']:
if self.requires_system_checks == ALL_CHECKS:
self.check()
else:
self.check(tags=self.requires_system_checks)
if self.requires_migrations_checks:
self.check_migrations()
# 最核心的即是 handle 方法
output = self.handle(*args, **options)
if output:
if self.output_transaction:
connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
output = '%s\n%s\n%s' % (
self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()),
output,
self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
)
self.stdout.write(output)
return output
7、excute() 方法存在于 BaseCommand 这个类中
8、BaseCommand 被 django.core.management.commands 下的每一个py中的Command类继承
9、excute()方法中的handle方法实际存在于每一个子类中
10、实际最后我们将执行某个 Command 类中的 handle 方法
7、总结
1、如果当前命令为 python manage.py startapp app_xx
2、则实际最后执行的是 django.core.management.commands文件夹下的 startapp.py,实例化里面的 Command 这里类,并且调用 handle 方法,下一节将讨论 handle 方法