Django2.0のプロジェクトのurls.pyにおける、include()での引数namespaceについて調べてみた

Django 2.0にて、プロジェクトの urls.py

from django.urls import path, include

urlpatterns = [
    path('old/', include('myapp.urls', namespace='old')),
    ...
]

アプリの urls.py

from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    path('namespace/',
         TemplateView.as_view(template_name='myapp/url_with_include.html'),
         name='without_app'),
]

としたところ、以下のエラーが発生しました。

'Specifying a namespace in include() without providing an app_name '
django.core.exceptions.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.

 
この時に調査・対応したことをメモしておきます。

 
目次

 

環境

 

調査

エラーメッセージより、

  • includeで呼ばれるモジュールに、 app_name を設定
  • (URLパターン, 名前空間) のタプルをinclude()に渡す

のどちらかを実装すれば良さそうでした。

 
また、 django.urls.include() の公式ドキュメントを見たところ、引数 namespace を使いたい場合の記載もありました。
include() | django.urls functions for use in URLconfs | Django documentation | Django

ただ、エラーメッセージで示されたどちらかの実装をしていないと、引数 namespace が使えないようです。

 
次に、いつから app_name が導入されたのかを調べてみたところ、 1.9のようでした。
https://docs.djangoproject.com/en/1.9/topics/http/urls/#url-namespaces-and-included-urlconfs

1.8以前は、include()の引数に namespace を使う方法が書かれていました。
https://docs.djangoproject.com/en/1.8/topics/http/urls/#url-namespaces-and-included-urlconfs

 
チュートリアルも、1.9から書き直されていました。

 
最後に、いつ頃からエラーが発生する用になったのかと思い、挙動を確認したところ、

でした。

Django 2.0のリリースノートにある

Support for setting a URL instance namespace without an application namespace is removed.

https://docs.djangoproject.com/en/2.0/releases/2.0/#features-removed-in-2-0

のあたりが関係していそうでした。

 
以上より、冒頭のソースコードはDjango2.0で1.8時代の実装をしていたために発生していたようでした。

 

対応

調査方法でも書きましたが、主な対応方法として以下の3つが考えられました。

  • プロジェクトのurls.pyのinclude()では namespace 引数を使用せず、アプリのurls.pyに app_name を追加
  • プロジェクトのurls.pyのinclude()の引数として、 (アプリのurls.py, 名前空間) となるタプルを渡す
  • プロジェクトのurls.pyのinclude()に namespace 引数を使用し、アプリのurls.pyでも app_name を追加

 
どの方法が良いのかと思い、Djangoの公式チュートリアルを見たところ、アプリのurls.pyに app_name を追加する書き方をしていました。
https://docs.djangoproject.com/ja/2.0/intro/tutorial03/#namespacing-url-names

そのため、特に制限がなければ、アプリのurls.pyに app_name を書くのが良いのかなと思いました。

 

動作確認

今回は、url テンプレートタグを使った時にいずれの方法でもURLリバースできるか にて確認してみます。

 

実装

まず、プロジェクトのurls.pyは

urlpatterns = [
    # アプリのurls.pyに app_name 設定があるパターン
    path('with/', include('myapp.urls_with_app_name')),

    # アプリのurls.pyには app_name 設定がなく、includeの引数をタプルにして名前空間を渡すパターン
    # (urlpatternsのあるモジュール, 名前空間)
    path('without/', include(('myapp.urls_without_app_name', 'without'))),

    # アプリのurls.pyに app_name 設定があるが、名前空間を別に用意するパターン
    path('over/', include('myapp.urls_overwrite_app_name', namespace='replaced')),
    # アプリのurls.pyの app_name 設定をそのまま使うパターン(再掲)
    path('not_over/', include('myapp.urls_overwrite_app_name')),
]

とします。

 
次に、app_nameのあるアプリのurls.pyはそれぞれ

myapp.urls_with_app_name.py

app_name = 'with'

# urlpatternsは、他も同じなので、以下省略
urlpatterns = [
    path('namespace/',
         TemplateView.as_view(template_name='myapp/url_with_include.html'),
         name='with_app'),
]

 
myapp.urls_overwrite_app_name.py

app_name = 'overwrite'

とします。

 
あとは、テンプレートを

<ul>
    <li><a href="{% url 'with:with_app' %}">app_name がある場合</a></li>
    <li><a href="{% url 'without:without_app' %}">app_name がない場合</a></li>
    <li><a href="{% url 'replaced:over_app' %}">app_name が上書きされた場合</a></li>
    <li><a href="{% url 'overwrite:over_app' %}">app_name がそのままの場合</a></li>
</ul>

として用意します。

 

動作確認

開発サーバを起動し、curlでアクセスしてみると、いずれの方法でもURLリバースできていました。

$ curl -L http://localhost:8000/with/namespace
<!DOCTYPE html>
...
<ul>
    <li><a href="/with/namespace/">app_name がある場合</a></li>
    <li><a href="/without/namespace/">app_name がない場合</a></li>
    <li><a href="/over/namespace/">app_name が上書きされた場合</a></li>
    <li><a href="/not_over/namespace/">app_name がそのままの場合</a></li>
</ul>

 

ソースコード

GitHubに上げました。
https://github.com/thinkAmi-sandbox/Django20-sample