mysite流そうと思ったのですがなかなか面白いことをしているのでちゃんと読むことにします。いうわけで、django-admin。management.execute_from_command_line()というわけで、core/managementに移動。fooとした場合、fooディレクトリに__init__.pyがあればそれがインポートされるfooディレクトリに__init__.pyがなく、fooディレトクリと同じ階層にfoo.pyがあればそれがインポートされるという動作をします。ちなみに、__init__.pyがなく、foo.pyもないと場合、エラーにはならないようです。あれ?昔は__init__.py入れとかないでディレクトリ内のpyファイルインポートしようとしたらエラーになった気がするけど。execute_from_command_lineは一番下に書かれています。utility.execute()ManagementUtilityはすぐ上にあります。Nonesys.argv[:]って何やってるのかと思いましたが、[:]と書くと先頭要素から最終要素までの部分リストが返される、つまり、リストのコピーが作成されるようです。settings.configure()まだファイルも何も作ってないのだから例外に行きそうなのはわかりますがsettingsの中身を追っかけてみましょう。†settingsはファイルの最後に書かれています。LazySettings()Lazy、よくある遅延処理ですね。LazySettingsはファイルの上の方に書かれています。Settings(settings_module)説明の都合上メソッドの順番を入れ替えましたが、LazyObjectは汎用の遅延初期化用クラスで_wrappedに遅延対象のオブジェクトを格納しています(django/utils/functional.pyに書かれています)。INSTALLED_APPSという属性を取ろうとすると__getattr__が呼ばれ、まだ初期化されていないので_setupが呼ばれ、settings.pyはないので例外送信、ということになります。普通の条件分岐っぽい処理を例外処理でやってるのに違和感を感じるのですがエラーの場合は例外を投げるのがPython流らしいですね。いうわけで例外が送信されて、managementの方に戻ってきて、定義されている「settingsの要らないコマンド」なので今度はsettingsのconfigureが呼ばれます。なんか設定が不適切という例外が飛んできたのにconfigure呼ぶって違和感を感じます。コメントにあるようにload_defaultとかにすればいいのに。emptysettings要らないって言ってるのだからデフォルト値も要らない気がしますが、いろんなところで参照されているから初期化しているのでしょうね。†settingsの初期化が終わるとdjango.setupが呼ばれていますが、今回はまだ設定がないので何もしないだろうということで無視します。残りはコマンドラインで指定されたコマンドに応じた分岐です。self.fetch_command(subcommand).run_from_argv(self.argv)というわけでfetch_commandに続く。klassまずはget_commandsへ。get_commandsはManagementUtilityクラスのメソッドではなくただの関数です、念のため。commands__path__はパッケージの__init__.pyがあるディレクトリが入っているそうです。upathはUnicodeなパスになるようにしているだけなのようなので詳細省略、find_commandsに行きます。name.startswith('_')]パッケージのディレクトリ、つまり、django/core/managementの中にあるcommandsディレクトリの各モジュールをリストアップしています。'django.core'}みたいな辞書ができるわけですね。fetch_commandに戻る。†さて、というわけでようやくstartprojectコマンドを実行するモジュールにたどり着きました。run_from_argvは直接このモジュールには書かれていません。startproject.Commandは、django.core.management.templates.TemplateCommand(templates.pyに記載)のサブクラスで、TemplateCommandはdjango.core.base.BaseCommand(base.pyに記載)のサブクラスです。BaseCommandを見るとコマンドがどのように実行されるかが書かれています。handleという順番に実行され、サブクラスはadd_argumentsをオーバーライドして必要な引数を指定、handleをオーバーライドしてコマンド処理を実行するようです。connections.close_all()parserはargparse.ArgumentParser(を少し拡張したクラス)で、parse_argsによりNamespaceオブジェクトが返されます。varsはオブジェクトのメンバー辞書(__dict__)を取得する関数です。上で示したように、create_parserはadd_argumentsを呼び出します。BaseCommandのadd_argumentsは空ですがTemplateCommandでオーバーライドされています。後から出てくるので、ちゃんと見ておきましょう。mysiteとした場合、nameに'mysite'、directoryとtemplateにNoneが入ります。†executeはシステムチェックとかが行われていますが今回はコマンドの設定により行われないし今回の注目点でもないので省略。それらを省くとexecuteはhandleを呼び出しているだけです。いうわけで、startprojectモジュールのhandleにやってきました。**options)validate_nameはプロジェクト名として指定された名前がPythonの識別子として使えるかをチェックしています。識別子として使えない場合は例外が投げられて呼び出し元で適切にハンドリングがされます。autoescape=False)Contextはdjango.templateのクラスです。省略というわけで、django/conf/project_templateにあるファイルがテンプレートとして使用されます。handleの中盤に話を戻すと、テンプレートディレクトリと同じディレクトリ構造、ただし、「project_name」の部分はプロジェクトの名前で置き換え、ということをしています。省略テンプレートの中身に踏み込むのはまた今度。これでテンプレートディレクトリにあるファイルがプロジェクトディレクトリにコピーされました。†ということでDjangoのとっかかり、startprojectの処理を見てきました。Pythonに慣れてないと「なんでこんな風に書いてあるの?」という部分がいくつかありましたが、本質的な部分はrailsと同じに思いました。・・・いや、黒魔法が少ない分Railsより簡単かな?(笑)なお、startappについてはmanage.pyを使う、プロジェクトのsettings.pyがあるという違いはありますが、やってることは9割以上同じなので飛ばします。