きっかけはDocker, docker-composeを利用してコードを書いてみようと思ったときに、公式サンプルのコードを見ての疑問でした。
sudo docker compose run web django-admin startproject composeexample .
https://github.com/docker/awesome-compose/tree/master/official-documentation-samples/django/ より
Djangoのチュートリアルでは以下のように記述されているのでそのまま使っていたので、 .
ってなんだったかな? という感じでした。
$ django-admin startproject mysite
https://docs.djangoproject.com/en/5.0/intro/tutorial01/#creating-a-project
結論、.
についてはLinuxコマンド上での、カレントディレクトリのことで、冒頭のコードとしては現在のディレクトリにprojectを作成する、ということでした。
コードを見てみる
startproject
で実行されるのは django/core/management/commands/startproject.py
です。
startprojectのコード自体は結構シンプルです。
from django.core.checks.security.base import SECRET_KEY_INSECURE_PREFIX from django.core.management.templates import TemplateCommand from ..utils import get_random_secret_key class Command(TemplateCommand): help = ( "Creates a Django project directory structure for the given project " "name in the current directory or optionally in the given directory." ) missing_args_message = "You must provide a project name." def handle(self, **options): project_name = options.pop("name") target = options.pop("directory") # Create a random SECRET_KEY to put it in the main settings. options["secret_key"] = SECRET_KEY_INSECURE_PREFIX + get_random_secret_key() super().handle("project", project_name, target, **options)
https://github.com/django/django/blob/main/django/core/management/commands/startproject.py
ここでは
from django.core.management.templates
からTemplateCommand
を継承してCommand
を作成していることname
,directory
をoptions
から取得して基底クラスのhandle
を呼び出し・渡している
ことが分かります。
基底クラスのhandle
を呼び出し・渡した後
TemplateCommand
のhandle
の冒頭部分は以下のようになっています。
def handle(self, app_or_project, name, target=None, **options): self.app_or_project = app_or_project self.a_or_an = "an" if app_or_project == "app" else "a" self.paths_to_remove = [] self.verbosity = options["verbosity"] self.validate_name(name) # if some directory is given, make sure it's nicely expanded if target is None: top_dir = os.path.join(os.getcwd(), name) try: os.makedirs(top_dir) except FileExistsError: raise CommandError("'%s' already exists" % top_dir) except OSError as e: raise CommandError(e) else: top_dir = os.path.abspath(os.path.expanduser(target)) if app_or_project == "app": self.validate_name(os.path.basename(top_dir), "directory") if not os.path.exists(top_dir): raise CommandError( "Destination directory '%s' does not " "exist, please create it first." % top_dir )
この else
以降の処理がディレクトリ名を引数として渡したときに実行される箇所ですが、os.path.abspath(os.path.expanduser(target))
という内容によって実際にディレクトリ名を指定・取得しているようでした。
https://github.com/django/django/blob/main/django/core/management/templates.py#L103-L111
とはいえパッと見て理解できなかったので、それぞれ分割して動かしてみると、以下のような挙動でした。
.
のケース
>>> os.path.expanduser('.') '.' >>> os.path.abspath('.') '/Users/tatsuya/django'
プロジェクト名
のケース
>>> os.path.expanduser('djangoproject') 'djangoproject' >>> os.path.abspath('djangoproject') '/Users/tatsuya/django/djangoproject'
動かすだけではなくリファレンスについても見ておきましょう。
expanduser
についてはこちら。
Unix および Windows では、与えられた引数の先頭のパス要素 ~ 、または ~user を、 user のホームディレクトリのパスに置き換えて返します。
https://docs.python.org/ja/3.12/library/os.path.html#os.path.expanduser
abspath
についてはこちら。
パス名 path の正規化された絶対パスを返します。
https://docs.python.org/ja/3/library/os.path.html#os.path.abspath
自分の中では完全理解出来たかな、と言う状態です。
expanduser
はどちらかというと ~
、~user
を渡した時の挙動に関係するため、使わなくても良いのでは?と個人的に思いましたが、そこまで調べるのは時間がかかりそうなので一旦「こういう感じなんだな〜」と留めることにします。
あとは個人的に、目的別にCommand
が存在していることが分かって面白かったので、 Command
のコード周りを読むのも面白そうだなと感じました。