前書き
今回の記事を書くに至る経緯を説明します、teratailでこんな質問がありました、
Djangoのモデルを有効にするときのプロジェクトへの教え方が分からない
質問の内容に関しては以下の通りでした。
DjangoのINSTALLED_APPSのモジュールは何故
app名前.apps.app名前config
ように入れているだろう、
ディレクトリ構成で言えば../app名前/apps.app名前config
のはずではないか。
よって、INSTALLED_APPS
に関するDjangoのソースコードを説明したいと思います。
Djangoのコアに興味ある方はぜひ最後までお付き合いください。
下準備
解説するため、django_test
という簡単なプロジェクトを作ります、ディレクトリ構成は以下のようです。
djnago_test
|-- django_test
|-- |-- __init__.py
|-- |-- asgi.py
|-- |-- settings.py
|-- |-- urls.py
|-- |-- wsgi.py
|-- manage.py
runserver
Djangoのプロジェクトをコマンドラインから起動します。
python manage.py runserver
では、その後何が起こったのでしょうか、manage.py
の中身を見てみましょう。
# !/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
-
setdefault
はenviron
にkey、valueの辞書形データを追加します、もしkeyが存在する場合、valueを取得します。 -
management
モジュールをインポート。 -
execute_from_command_line(sys.argv)
を実行します。
まず、sys.argv
の中身から見ていきます、manage.pyのpathと実行コマンドです。
['manage.py', 'runserver']
次はexecute_from_command_line(sys.argv)
の中身を見ます。
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
utility = ManagementUtility(argv)
utility.execute()
内容は非常にシンプルです、sys.argv
の値を受け取って、
ManagementUtility
をインスタンス化して、execute
関数を実行しました。
ManagementUtility
の __init__
関数を見ていきます。
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
この場合:
-
self.arg
==sys.argv
==['manage.py', 'runserver']
-
self.prog_name
==manage.py
のpath
次を見ていきます。
execute
関数は何をやったのでしょう、関数の中身は非常に長いので重要な箇所だけ説明します。
try:
subcommand = self.argv[1]
except IndexError:
subcommand = 'help'
python manage.py runserver
実行する際に、subcommandの値はrunserver
になります。
そして、下記のブロックに入ります。
...
if subcommand == 'runserver' and '--noreload' not in self.argv:
try:
autoreload.check_errors(django.setup)()
...
django.setup
の中身を見ていきます。
...
def setup(set_prefix=True):
"""
Configure the settings (this happens as a side effect of accessing the
first setting), configure logging and populate the app registry.
Set the thread-local urlresolvers script prefix if `set_prefix` is True.
"""
from django.apps import apps
from django.conf import settings
from django.urls import set_script_prefix
from django.utils.log import configure_logging
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
if set_prefix:
set_script_prefix(
'/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME
)
apps.populate(settings.INSTALLED_APPS)
ここまできたら、 INSTALLED_APPS
に関する処理が明らかになってきました、
populate
のソースを見ていきます、そこそこ長いので、重要な箇所だけ説明します。
...
def populate(self, installed_apps=None):
...
for entry in installed_apps:
if isinstance(entry, AppConfig):
app_config = entry
else:
app_config = AppConfig.create(entry)
if app_config.label in self.app_configs:
raise ImproperlyConfigured(
"Application labels aren't unique, "
"duplicates: %s" % app_config.label)
self.app_configs[app_config.label] = app_config
app_config.apps = self
...
こちらのinstalled_apps
の中身は下記のものになります、ループして、一個づつ取り出して次の処理に入ります。
一つ注意すべき点は'django.contrib.admin'
のような内容は、タイプは文字列です。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
ループの中身は、INSTALLED_APPS
に入ってる要素に対する継承関係の条件判断になります。
if isinstance(entry, AppConfig):
app_config = entry
else:
app_config = AppConfig.create(entry)
もちろん、現在はただの文字列なので、elseのブロックに入ります。
app_config = AppConfig.create(entry)
create()
の中身を見てみます。
def create(cls, entry):
...
try:
module = import_module(entry)
except ImportError:
module = None
mod_path, _, cls_name = entry.rpartition('.')
if not mod_path:
raise
...
ここまできたら、 import_module
関数を実際使ってみたいと思います、
users
appを作り、それを INSTALLED_APPS
に追加します。
python manage.py startapp users
INSTALLED_APPS= [
...
users or users.apps.UsersConfig
]
users
として追加される場合。
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')
from importlib import import_module
print(import_module("users"))
# <module 'users' from '/Users/user/django/django_test/users/__init__.py'>
ちゃんとappを見つけてくれました。
users.apps.UsersConfig
として追加される場合。
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')
from importlib import import_module
print(import_module("users.apps.UsersConfig"))
# ModuleNotFoundError: No module named 'users.apps.UsersConfig'; 'users.apps' is not a package
app見つからないようです、よって、以下のブロックに入ります。
except ImportError:
module = None
mod_path, _, cls_name = entry.rpartition('.')
if not mod_path:
raise
mod_path, _, cls_name = entry.rpartition('.')
が実行され、
mod_path
の値がusers.apps
になり、cls_name
が UsersConfig
になります。
再度テストしてみます。
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings')
from importlib import import_module
mod_path, _, cls_name = "users.apps.UsersConfig".rpartition('.')
print(import_module(mod_path))
# <module 'users.apps' from '/Users/user/django/django_test/users/apps.py'>
appを見つけてくれました。