10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Django--INSTALLED_APPSに関してのソース解析

Posted at

templarbit-illustration-django-1a748506.jpg

前書き

今回の記事を書くに至る経緯を説明します、teratailでこんな質問がありました、
Djangoのモデルを有効にするときのプロジェクトへの教え方が分からない

質問の内容に関しては以下の通りでした。

DjangoのINSTALLED_APPSのモジュールは何故app名前.apps.app名前configように入れているだろう、
ディレクトリ構成で言えば ../app名前/apps.app名前configのはずではないか。

よって、INSTALLED_APPSに関するDjangoのソースコードを説明したいと思います。
Djangoのコアに興味ある方はぜひ最後までお付き合いください。:relaxed:

下準備

解説するため、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の中身を見てみましょう。

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()
  • setdefaultenvironに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)の中身を見ます。

management/__init__.py
def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    utility = ManagementUtility(argv)
    utility.execute()

内容は非常にシンプルです、sys.argvの値を受け取って、
ManagementUtilityをインスタンス化して、execute 関数を実行しました。
ManagementUtility __init__関数を見ていきます。

management.py
    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関数は何をやったのでしょう、関数の中身は非常に長いので重要な箇所だけ説明します。

management.py
try:
    subcommand = self.argv[1]
except IndexError:
    subcommand = 'help'

python manage.py runserver 実行する際に、subcommandの値はrunserverになります。
そして、下記のブロックに入ります。

management.py
...
if subcommand == 'runserver' and '--noreload' not in self.argv:
   try:
      autoreload.check_errors(django.setup)()
...

django.setupの中身を見ていきます。

django/__init__.py
...
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のソースを見ていきます、そこそこ長いので、重要な箇所だけ説明します。

registry.py
...
    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() の中身を見てみます。

config.py
    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関数を実際使ってみたいと思います、
usersappを作り、それを INSTALLED_APPS に追加します。

python manage.py startapp users
INSTALLED_APPS= [
...
users or users.apps.UsersConfig
]

usersとして追加される場合。

test.py
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として追加される場合。

test.py
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見つからないようです、よって、以下のブロックに入ります。

config.py
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_nameUsersConfigになります。
再度テストしてみます。

main.py
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を見つけてくれました。

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?