目次
対象読者
- includeメソッドの内部処理に興味がある方
-
startprojectコマンドで生成されるconfig/urls.pyから各アプリへ URL を委譲する仕組みを理解したい方
なにはともあれincludeメソッドの中身を見ていこうではないか
まずは実際のコードを確認しておきます。Django 公式が提供している include() 関数は以下のように実装されています。
site-packages/django/urls/conf.py
def include(arg, namespace=None):
app_name = None
if isinstance(arg, tuple):
# Callable returning a namespace hint.
try:
urlconf_module, app_name = arg
except ValueError:
if namespace:
raise ImproperlyConfigured(
"Cannot override the namespace for a dynamic module that "
"provides a namespace."
)
raise ImproperlyConfigured(
"Passing a %d-tuple to include() is not supported. Pass a "
"2-tuple containing the list of patterns and app_name, and "
"provide the namespace argument to include() instead." % len(arg)
)
else:
# No namespace hint - use manually provided namespace.
urlconf_module = arg
if isinstance(urlconf_module, str):
urlconf_module = import_module(urlconf_module)
patterns = getattr(urlconf_module, "urlpatterns", urlconf_module)
app_name = getattr(urlconf_module, "app_name", app_name)
if namespace and not app_name:
raise ImproperlyConfigured(
"Specifying a namespace in include() without providing an app_name "
"is not supported. Set the app_name attribute in the included "
"module, or pass a 2-tuple containing the list of patterns and "
"app_name instead.",
)
namespace = namespace or app_name
# Make sure the patterns can be iterated through (without this, some
# testcases will break).
if isinstance(patterns, (list, tuple)):
for url_pattern in patterns:
pattern = getattr(url_pattern, "pattern", None)
if isinstance(pattern, LocalePrefixPattern):
raise ImproperlyConfigured(
"Using i18n_patterns in an included URLconf is not allowed."
)
return (urlconf_module, app_name, namespace)
重要なポイントはここ
ここからは、引数 arg や namespace がどのように扱われているかを順番に追いながら、内部処理を解説します。
-
tupleかどうかの判定
include()は第一引数に「URLconf モジュール」または「URLパターンのリスト」を受け取ります。- もし
(urlconf_module, app_name)のような 2 要素タプルが渡されれば、2 番目の値がapp_nameとして扱われます。 - 要素数が 2 以外のタプルだった場合は
ImproperlyConfigured例外を投げて、正しい引数の形で呼び出すよう警告しています。 - タプルではない場合(一般的には 文字列でモジュールパスを渡す ケース)には、そのまま
urlconf_moduleとして扱います。
- もし
-
文字列なら動的 import
if isinstance(urlconf_module, str): urlconf_module = import_module(urlconf_module)ここが一番の肝です。例えば
include('modelonetomany.urls')と書くと、Django が自動的にmodelonetomany.urlsというモジュールを import してくれます。これにより、config/urls.py側で明示的な import 文を書く必要がありません。
import_module(urlconf_module)のおかげですね。 -
urlpatternsとapp_nameの取得patterns = getattr(urlconf_module, "urlpatterns", urlconf_module) app_name = getattr(urlconf_module, "app_name", app_name)- import したモジュールに
urlpatternsという属性があればそれを使用します。もしリストが直接渡されていれば、そのまま URL パターンとして扱います。 - 同様に
app_nameという属性が定義されていれば上書きします。これが URL 逆引き時の名前空間 に使われる値です。
- import したモジュールに
補足
- より深く理解したい場合は、以下の記事も合わせてご覧いただくと把握が進むと思います。