Django源代码阅读分析-1:命令行选项
声明:本文为转载
使用Django开始一个项目,用得最多的大概应该是django-admin.py命令了。使用它可以创建一个项目、清理项目、进入交互环境等等。想了解一个Django,以及如何用Python做一个优秀的项目和框架,我也打算从这里开始。由于我在项目中使用的是Django1.1.1,我就以这个版本作为蓝本。到现在为止,Django已经升级为1.2.1版了。
首先看看源代码目录结构,总体了解一下它的结构。有好多东西现在对我来说还不明朗,现在的理解可能还会有许多不准确的地方,可以随时更正。
- -Bin //可执行文件,django的PATH可以设置在这里,我们最常用的命令之一django-admin.py就在其中
- -Conf //这是对生成的一个Project和App的配置文件,包括建立Project或者App时候会拷贝到其下的Python代码模板。
- -Contrib //标准模块。就是说,没有它你也能活,有了它可以帮你减少很大的工作量。例如一个通用的Admin后台,用户认证组件,Session,站点地图等等。
- -Core //核心模块
- -Db //数据库接口,Django可以兼容很多数据库,包括MySQL、Oracle等等,甚至SQLite。Db中还包括数据模型Model的定义,使用这些定义,可以屏蔽底层DNMS的差异。
- -Dispatch //信号相关模块
- -Froms //表单处理相关模块
- -Http //Http请求和应答等
- -Middleware //中间件。可以辅助系统在处理request之前先执行某些处理。
- -Shortcuts //快捷方式,例如常用的render_to_response方法就在这里了。
- -Template和Templatetags //django模板引擎
- -Test //单元测试框架
- -Utils //实用小程序
- -Views //视图处理
使用Python setup.py install命令从源代码安装完Django后,这些都会被拷贝到Python安装目录下的Lib/site-packages/django子目录中。之后我们使用Django的第一条命令大概就是使用django-admin.py startproject projectname来创建一个工程,我就打算从这里切入开始吧。
django-admin.py命令可以使用的一系列参数对应的命令写在Django.core.Management.Commands命名空间中,在其中可以看到许多的模块,每个模块即是对应django-admin命令中的一个参数。
- 命令行调用命令django-admin.py subcommand [options] [args]
- 初始化ManagementUtil,并调用其execute()方法
- 对参数进行解析并验证,调用fetch_command()方法获取对应的Command
- 根据参数import对应的Model,导入操作使用的就是Python内置的__import__
- 调用core.management.Base.py模块中的BaseCommand.run_from_argv()方法
- run_from_argv()方法调用create_parser()创建一个解析器,解析参数和配置环境,并再调用execute()方法
- execute()方法调用handle()方法执行命令,handle()方法在BaseCommand类中的实现只是简单的抛出异常,所以BaseCommand的各个子类必须覆盖此方法才能使用这个命令。
特点:采用命令模式实现。
我们选一个Command来看看它是怎么实现的。就拿最简单的之一,也是最开始必用的Startproject.py命令吧。
class Command(LabelCommand): help = "Creates a Django project directory structure for the\ given project name in the current directory." args = "[projectname]" label = 'project name' requires_model_validation = False # Can't import settings during this command, because they haven't # necessarily been created. can_import_settings = False def handle_label(self, project_name, **options): # Determine the project_name a bit naively -- by looking at the name of # the parent directory. directory = os.getcwd() # Check that the project_name cannot be imported. try: import_module(project_name) except ImportError: pass else: raise CommandError("%r conflicts with the name of an existing Python module \ and cannot be used as a project name. Please try another name." % project_name) copy_helper(self.style, 'project', project_name, directory) # Create a random SECRET_KEY hash, and put it in the main settings. main_settings_file = os.path.join(directory, project_name, 'settings.py') settings_contents = open(main_settings_file, 'r').read() fp = open(main_settings_file, 'w') secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz\ 0123456789!@#$%^&*(-_=+)') for i in range(50)]) settings_contents = re.sub(r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents) fp.write(settings_contents) fp.close()
这是LabelCommand的一个子类,只有一个方法:handle_label()。在LabelCommand类的实现中已经覆盖了BaseCommand的handle()方法,但是暴露了这个handle_label()仍然会抛出异常,需要它的子类去实现。
class LabelCommand(BaseCommand): args = '
- 拷贝对应的模板目录中的文件到指定名字的工程目录下。
- App命令:用于维护App的,如果继承自BaseCommand则必须实现handle()方法,如果继承自AppCommand则必须实现handle_app()方法;