入門クラス

【Django】urls.pyとは

Djangoにおいて重要かつ複雑そうに見えて実はそれほど難しくないのが、デフォルトではurls.pyに設定されている項目です。

この記事ではurls.pyに設定されている内容について解説してきます。

urls.pyに記載する内容のことを便宜上urls.pyとファイル名で呼んでいます

urls.pyの役割

urls.pyはクラスベースビューはもちろん関数で作成されたViewとURLを連携させる役割を持っています。

Djangoはウェブアプリケーションフレームワークですので、ウェブサイトを構築します。
構築されたウェブサイトにはURLが設定されていますよね。そのURLに応じてViewを呼び出します。

どのURLに対してどのViewを呼び出すかをurls.pyに定義します。

それがurls.pyの役割です。

 

例えば、

  • https://foo.com/ にアクセスされたとき、ArticleTemplateViewを実行する
  • https://foo.com/blog/ にアクセスされたときはArticleListView を実行する

といったURLとの連携をさせる、ということです。

次の項から実際に書き方を通じて理解を深めていきます。 

urls.pyはURLとViewを連携させるための定義を記述する

モデルとViewの定義

urls.py はURLとViewを連携させるため、あらかじめViewを定義しておく必要があります。

Viewを定義するにはモデルが必要です。

ということで、ここでは以下のようなモデルとViewを定義します。

モデルはこんな感じだと思ってください。

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

 

Viewはこんな感じです。

from django.views.generic import ListView

from .models import Article

class ArticleListView(ListView):
    model = Article

このような定義がされているとして、以下の解説に進みます。

ListViewについては以下の記事を参考にしてください。

urls.pyの書き方

では実際に記述していきます。

Django 3.2 で記述しています。

プロジェクトを作成した直後のurls.pyはこのようになっているはずです。

from django.contrib import admin
from django.urls import path
urlpatterns = [
    path('admin/', admin.site.urls),
]

ここに先ほどのViewを追加して、その後解説を加えていきます。

from django.contrib import admin
from django.urls import path
from bar.views import ArticleListView  # インポート
urlpatterns = [
    path('articles/', ArticleListView.as_view()),  # この行を追加
    path('admin/', admin.site.urls),
]

4行目と7行目を追加しました。

4行目は作成したArticleListViewをインポートしています。

7行目が定義部分です。

path('articles/', ArticleListView.as_view()),

この部分ですね。

path関数

path関数はURLとViewを連携させる関数です。

定義のしかたは以下の通りです。

path(連携させるURL, 対応するView)

path関数の第一引数は連携させるURLを指定します。
今回はarticles/としています。

http://foo.com/ のようなドメイン部分は?と思われるかもしれませんが、指定の必要はありません。
そこより下位のURLを指定してください。

第二引数の対応するViewですが、今回はArticleListView.as_view()を指定しています。

この指定によってURLとViewが連携し https://foo.com/articles/ にアクセスされると、ArticleListViewが動作してページを表示します。

これがURLとViewを連携させる、ということです。

urlpatternsとは

urls.py で重要な部分は2つあります。

一つはpath関数であり、もうひとつはurlpatternsという変数です。

この変数はpath関数のリストになっていて、path関数によって連携を構築した設定を格納します。

しかし、リストであることは変わりありませんので、path関数を後で追加することも可能です。

 意味が分からないと思いますので、コードで示します。

from django.contrib import admin
from django.urls import path
from bar.views import ArticleListView
urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += [
    path('articles/', ArticleListView.as_view()),
]

 このようにして、はじめに定義したurlpatternsに対して追加でpath関数のリストを追加することができます。

urls.pyの書き方が変わったことについて(Django 2.0より)

urls.py の書き方は Django 2.0 から大きく変更されました。

Django 1.x の時代は url関数で正規表現を使ってアクセスされたURLを定義しましたが、Django 2.xからはもう少しわかりやすい表記に変更されています。Django 1.x 時代のurl関数はre_pathという関数で実行可能です。

いまどきDjango 1.x 系を使用しているプロジェクトは多くないでしょうが、注意してください。

関数のViewをurls.pyに指定する

ネットの情報などを検索すると、関数でViewを記述している例もたくさん見つかります。
公式ドキュメントのチュートリアルでも使用されています。

例えば、以下のようなViewです。

def sample(request):
    return HttpResponse('Hello')

このようなViewは今でも記述されているメジャーな記述方法です。

ほとんどの場合クラスベースビューで対応できますが、それだとややこしくなってしまう場合などに使用します。

こういった関数のViewをurls.pyに定義するときは、以下のようにします。

path('sample-url/', sample),

ポイントとしては、関数で作成したViewの場合、呼び出してはならない、ということです。

カッコを記述してはいけない、ということですね。

カッコを記述しているとエラーになりますので注意してください。

関数のViewをpath関数に記述するときは、Viewの関数を呼び出してはならない(カッコを書いてはいけない)

個別ページのurls.py

例としてListViewを記述しましたが、個別ページのURLは記述がすこし違います。

今回は例としてDetailViewを取り上げてみます。単一のページを扱うViewであればほとんどのクラスベースビューに該当する内容です。

Viewに記述を追加しましょう。

こんな感じです。

from django.views.generic import ListView, DetailView  # DetailView を追加
from .models import Article
class ArticleListView(ListView):
    model = Article
# 以下を追記
class ArticleDetailView(DetailView):
    model = Article

DetailViewを追加しました。

DetailViewについては以下の記事を参考にしてください。

DetailViewの特徴としては、単一の記事を取得して表示します。
それを踏まえて、urls.pyを記述してみましょう。

urlpatternsなどは上記と変わりありませんので、path関数部分だけ解説します。

path('<int:pk>/', ArticleDetailView.as_view()),

このように記述します。

注目すべきはURLの部分で、<int:pk>と定義している部分です。

これは、URLのこの部分に数字が入り、さらにそれをpkという変数に格納する、という意味になります。

2つの意味がありますので1つずつ説明していきます。

URLに数字が入るとは

これは単純にこういったURLに反応する、ということです。

https://foo.com/1/

このように<int:pk>部分に数字が入る、という定義の仕方です。

ここには、数字であればどのような数でも入ります。ですので、100でも1000でもOKです。

逆に、1〜99までにしたい、といったニーズには応えられません。

そういった場合はre_pathを使用して正規表現で指定します。

また、仕様上0ということはあり得ません。なぜあり得ないのかについては後の項で説明します。

pkという変数に格納するとは

クラスベースビューでは内部でpkという変数を保持しています。

URLに<int:pk>のよう定義があるときは、その値を自動的に取得してクラスベースビューが保持している変数pkに代入します。

例えば、

https://foo.com/1/

のようなURLにアクセスされると1が指定されたViewに渡されます。

クラスベースビューが受け取った場合は自動的に変数に格納され、自身の動作に使用されます。

関数で作成されたViewであれば、第二引数に渡されます。関数を作成する際に第二引数を記述して使用します。

def sample2(request, pk):
    return HttpResponse('hello 1')

のように定義すれば、pk1が代入されます。

urls.pyの仕組み

前項ではURLで取得した値がpkに代入される事を説明しました。

では、なぜそのような動作をするのでしょうか?

これには前提知識が必要となりますので、そこから解説していきます。

データベースのレコードとプライマリーキー

このお話をするにはデータベースの基礎知識が必要になります。

データベースとモデルは密接な関係がありますので、まずはモデルを示したいと思います。上の記事で示したモデルと同じです。

モデルはこんな感じだと思ってください。

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

Djangoはこのモデルを通してデータを保存します。

さて、「データベースに保存されたデータ」というとき、どのようなイメージを描くでしょうか。

イメージの描き方はひとりひとり違うでしょうが、私はこのようなイメージを描きます。

IDtitlecontent
1sample 1sample 1 content
2sample 2sample 2 content
データベースに保存されたデータのイメージ

Excelの表みたいですね。

モデルに定義されているフィールド「title」や「content」が保存されていますね。データが保存されていくたびに1行1行増えていきます。

ここで重要な部分は、モデルには「ID」というフィールドを定義していないにもかかわらずデータが保存されています。
実は「ID」はモデルに定義しなくても自動で設定されるフィールドです。

このフィールドはデータが保存されていくたびに1づつ値が増加していくフィールドです。
また、データが削除されても同じ値が使用されることはなく欠番となります。

例えば、

IDtitlecontent
1sample 1sample 1 content
2sample 2sample 2 content
3sample 3sample 3 content
4sample 4sample 4 content
5sample 5sample 5 content
データベースに保存されたデータを増やす

「1, 2, 3, 4, 5」とデータが増えた後に、「3」を削除したとします。

IDtitlecontent
1sample 1sample 1 content
2sample 2sample 2 content
4sample 4sample 4 content
5sample 5sample 5 content
データベースに保存されたデータを削除

そうするとこのような状態になりますね。
ここでもうひとつデータを追加するとどうなるでしょうか。

IDtitlecontent
1sample 1sample 1 content
2sample 2sample 2 content
4sample 4sample 4 content
5sample 5sample 5 content
6sample 6sample 6 content
データベースに6番目のデータを追加

このように「3」は欠番となり、あらたなデータは「6」として保存されます。

この番号は「1」から始まりますので「0」であることはありません。

この「ID」ですが、データベース的にはプライマリーキーとなっています。
つまり、この番号が分かればどのデータであるのか必ず識別できる、という意味です。日本語では「主キー」などと呼ばれます。

このプライマリーキーを使ってDjangoはデータを取得します。pkとはプライマリーキーの略語です。

ここまでがDjangoとデータベースの関係で必要な前提知識です。

クラスベースビューがデータを取得するとき

さて、この項は前項で説明したプライマリーキーがクラスベースビューでどのように使用されているか、です。

path関数に<int:pk>が定義され、そのURL、例えばhttps://foo.com/1/にアクセスがあった場合クラスベースビューは<int:pk>に相当する部分を取得して変数pkに保存します。<int:pk>ですのでpkです。
今回の場合はhttps://foo.com/1/ですので、「1」が取得した数値であり、クラスベースビューが変数に保存する値です。

クラスベースビューはpkに保存された値をプライマリーキーとして使用し、連携しているモデルのデータを読み出します。
pkに保存されたデータをデータベースから探索し、発見したらそのレコードを取得します。
取得されたレコードは変数objectに格納されてテンプレートに渡されます。

つまり、

  • https://foo.com/1/であればIDが1のデータを取得
  • https://foo.com/3/であればIDが3のデータを取得

する、という動作をします。

以下に図で示します。下手ですみません。

urls.pyとクラスベースビューが連携してデータベースからレコードを読み出す

データベースに保存されているID(プライマリーキー)は1から始まりますので、0は存在しません。
仕様上0が存在しない、というのはそういった理由です。

urls.pyに定義されたURLを生成する

見出しだけだと何のことか分からないかもしれません。

いままでクラスベースビューがURLの一部を取得する、という内容を解説しました。
今回は逆に、urls.pyに定義されたURLをテンプレートで生成しよう、という内容です。

ListViewで生成する

まずは、上記のArticleListViewで試してみましょう。

path('articles/', ArticleListView.as_view()),

このように定義していました。
ここにnameを追記します。

path('articles/', ArticleListView.as_view(), name='index'),

このnameは今回重要な役目を担っています。
まずは、解説なしでテンプレートの記述をしてみたいと思います。もちろん次の節で解説します。
シンプルにするため、URLを生成する部分だけ記述します。

{% url 'index' %}

このようにすれば、/articles/というURLが生成されます。
もちろんこれでは役に立たないので、以下のように使用します。

<a href="{% url 'index' %}">記事一覧</a>

これでリンクが作成できますね。

テンプレートタグ url

今回、テンプレートタグ {% url %}を使用しています。

これは、urls.pyから実際のURLを生成するテンプレートタグです。

path関数に追加したnameを元にURLを生成する、という機能を持っています。

{% url %} は URLを生成する

このテンプレートタグ {% url %} は以下のような構文です。

{% url 'nameに指定された名前' 引数1 引数2 ... %}

テンプレートタグはコンマではなく半角スペースで引数を設定します。

最初の引数はpath関数に定義してあるnameの名前を記述します。
文字列ですのでシングルクォーテーションかダブルクォーテーションで囲います。

引数が必要な場合は続けて引数を設定していきます。必要なければ記述する必要はありません。
引数はテンプレートで使用できる変数でもOKです。

ArticleListViewでは引数を使用しませんでしたので、後の記事で使用例を含めて解説します。

繰り返しですが今回は path関数が以下の通りになってす。

path('articles/', ArticleListView.as_view(), name='index'),

ですので、nameに設定されているindexをテンプレートタグurlに指定することで、path関数の第一引数に定義されているURLarticles/が生成されました。

最初のスラッシュは絶対URLの指定ですね。
path関数では相対URLでの指定になっていますが、出力は絶対URLとなっています。この辺はDjangoがうまく処理してくれているところです。

URLを生成するメリット

大きなメリットは、URLを変更したいときにurls.pyを修正するだけでよい事です。

現在の定義は以下の通りです。

path('articles/', ArticleListView.as_view(), name='index'),

もし、URLを変更したくなった場合、ここの定義を変更するだけで済みます。

path('articles_test/', ArticleListView.as_view(), name='index'),

こんなURLはあり得ませんが、この部分を変更すれば

{% url 'index' %}

のように生成されたURLはすべて変更されます。

URLを変更すること自体良くないことなのですが、変更漏れがなくなります。

DetaiViewなどの個別ページのURLを生成する

ListViewのようなひとつのページのリンクを生成する方法は分かりました。

ではDetailViewのように、その都度違うレコードを取得して表示するViewの場合はどのようになるのでしょうか。

まずは、urls.pyの設定から確認してみましょう。

今回もpath関数の定義から。

path('<int:pk>/', ArticleDetailView.as_view(), name='detail'),

このようにしました。

name='detail'を追加しています。

先ほどのListViewのpath関数とは違い、<int:pk>のように、URLに数値などの情報が入ります。
このような場合、どのように{% url %}構文を記述するか説明します。

例えばhttps://foo.com/1/のように、<int:pk>部分に1が入る場合のurl構文は以下の通りです。

{% url 'detail' 1 %}

name='detail'と定義されていますので、最初の引数にはdetailが入ります。ここは単純な文字列ですのでPython同様クォートしてください。

第二引数が今回の肝で、いまは数値の1が入っています。数値ですのでクォートしていません。
<int:pk>という定義ですので、数値です。

このように定義すると、生成されるURLは以下のようになります。

/1/

つまり、https://foo.com/1/のようなURLとなります。

なんとなく分かると思いますが、第二引数に渡された数値が<int:pk>部分に適用されています。

もう一例出したいので、path関数を書き換えてみましょう。

path('detail/<int:pk>/', ArticleDetailView.as_view(), name='detail'),

第一引数にdetailを追加してみました。

では、URLを出力してみましょう。

{% url 'detail' 1 %}

前回と同じです。この結果は以下の通りです。

/detail/1/

つまり、https://foo.com/detail/1/のようなURLを生成します。

path関数の内容と見比べてみてください。<int:pk>部分が数値で置き換えられたようなURLになっていると思います。

もちろん、これでは不便ですのでテンプレートでは引数に変数を使用します。

{% url 'detail' object.id %}

このような感じです。

テンプレートに記述する際は数値をハードコードすることはほとんどないと思います。
変数を利用して生成することがほとんどです。

urls.pyを分割する

プロジェクト開発を進めていくとたくさんのアプリケーションを定義していくことになります。
アプリケーションが増えていくことこそDjangoプロジェクトの成長だからです。

しかし、そうなるとURLの定義がとても大変になります。

様々なアプリケーションのURLをすべてひとつのファイルに記述していくととても長くなってしまい、見通しも悪くなります。

そういったことを防止するために、Djangoではurls.pyを分割することができます。

本項では分割について解説します。

分割のメリット

分割のメリットは2つあります。

ひとつめは、前述の通りURL定義の見通しが良くなることです。

このメリットは言わずもがなで、できる限り複雑さを排除すること重要です。

もうひとつは、ネームスペースを作成することができることです。

ネームスペースがあることによって、app1とapp2でname='detail'のような同じ名前のpath関数を定義できるようになります。

include関数

urls.pyを分割するにあたり、はじめに知っておかなければいけない関数があります。

それが、include関数です。

この関数は文字どおり他のurls.pyを読み込む事ができます。
読み込む際にネームスペースを作成することができる、というものです。

include 関数は他のurls.py(に相当するファイル)を読み込むことができる

使い方は以下のとおりです。

path('articles/', include("bar.article_urls", namespace='article')),

今までViewを設定していたところにinclude関数を記述します。

今回定義した内容は以下のとおりです。

path('設定するURL', include(読み込むurls.py, namespace='ネームスペースの名称')),

では解説していきます。

path関数の第一引数であるURLは同じです。このURLにアクセスされた際に動作します。

第二引数のinclude関数が今回の本丸ですね。

include関数の第一引数は文字列で、読み込むurls.py(に相当するファイル)を指定します。相当するファイル、と記載しているのはファイル名はurls.pyである必要はないからです。わかりやすい名前で良いと思います。今回はarticle_urls.pyとしています。
指定の仕方はPythonのモジュール指定と同じです。ファイルの改装をドットで表現し、拡張子.pyは必要ありません。

名前付き引数に指定してあるnamespaceはその名のとおりネームスペースの名前を設定します。今回はarticleとしています。

app_nameをincludeされる側に記述する

実はinclude関数でネームスペースを設定するだけではうまくいきません。

includeされる側にも設定が必要です。
それが、app_nameです。

app_nameは難しくなく、ネームスペースの名前を代入します。

例えば、今回の例は以下のとおりです。

app_name = 'article'

このように、include関数で定義したnamespace='article'app_name='article''article'が同じであるのは偶然ではありません。

ネームスペースの指定ですので、同じ名称を設定してください。

実際に分割してみる

さて、長々と説明してきましたが、実際に分割してみます。

元になるurls.pyは以下のとおりです。

from django.contrib import admin
from django.urls import path
from bar.views import ArticleListView, sample, ArticleDetailView
urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += [
    path('', ArticleListView.as_view(), name='index'),
    path('<int:pk>/', ArticleDetailView.as_view(), name='detail'),
    path('sample-url/', sample),
]

ここまでの記事で出てきた内容です。

現在のファイルツリーは以下のとおりです。
一部作業に必要の無いファイルは意図的に表示していません。
カッコの中は説明を追加したものです。

├── bar (アプリケーション)
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── foo (プロジェクト)
│   ├── init.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py (分割するファイル)
│   └── wsgi.py
└── manage.py

まずは、barアプリケーションにarticle_urls.pyを作成します。
一から作成するのが面倒な方はfooプロジェクトのurls.pyをコピーして、不要なコードを削除しても良いと思います。私はコピーして使っています。

次に、fooプロジェクトのurls.pybarアプリケーションのarticle_urls.pyを更新します。
まずは、元々あるurls.pyは下記の通りです。

from django.contrib import admin
from django.urls import path, include  # include 追加
from bar.views import ArticleListView, sample, ArticleDetailView
urlpatterns = [
    path('admin/', admin.site.urls),
]
urlpatterns += [
    # 下の行を追加
    path('articles/', include("bar.article_urls", namespace='article')), 
    path('sample-url/', sample),
]

コメントで示していますが、include関数をインポートしています。
さらに、urlpatternsリストの内部にinclude関数を使ってarticle_urls.pyを読み込んでいます。
ネームスペースはarticleとしています。

続いて、先ほど新規作成したarticle_urls.pyは下記の通りです。

from django.urls import path

from bar.views import ArticleListView, ArticleDetailView

app_name = 'article'

urlpatterns = [
path('', ArticleListView.as_view(), name='index'),
path('<int:pk>/', ArticleDetailView.as_view(), name='detail'),
]

新たに作ったarticle_urls.pyにはクラスベースビューを使ったpath関数を設定しています。

現在のファイルツリーは以下の通りです。

├── bar (アプリケーション)
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── article_urls.py (新規追加したファイル)
│   └── views.py
├── foo (プロジェクト)
│   ├── init.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py (分割するファイル)
│   └── wsgi.py
└── manage.py

このようになっています。

反応するURLの違い

ここで注意点がひとつあります。

urls.pyでは連携させるURLを設定していました。

2つのurls.pyでは連携するURLが少し違います。例えばhttps://foo.com/article/1/で説明します。

https://foo.com /article/ 1/

元のurls.pyが連携している部分/article/ 部分です。

新規作成したarticle_urls.pyが連携している部分1/部分です。

図で示すと以下のとおりです。相変わらず下手な図ですみません。

include関数を活用した際のURLの処理

urls.pyarticle_urls.pyがURLを処理する際、どの部分を担当しているかご理解いただけたと思います。

urls.pyを分割することでネームスペースの作成と、includeしたファイルに処理を委譲することができます。

ネームスペースのある{% url %}

includeを使用するとネームスペースが作成されることがわかりました。

この状態でテンプレートに以下の記述をしてみましょう。

{% url 'detail' 1 %}

のように記述してもエラーとなります。
理由はネームスペースの記述がないからです。

では、そのネームスペースを含む {% url %}はどのように記述するのでしょうか。

答えは割と簡単で、以下のようにします。

{% url 'article:detail` 1 %}

これを自然言語で表すと以下のようになります。

{% url include関数で定義したネームスペース:path関数で定義したname 引数 %}

nameを定義する前に、名字のようにネームスペースを定義して、名字と名前をコロンで区切る、というわけです。

落ち穂拾い

分割するURLは必ずしもurls.pyというファイル名でなくても問題ありません。

では、最初から用意されているurls.pyはファイル名を変更しても良いのでしょうか?

答えは「良い」です。

しかし、設定が必要になります。

設定ファイルsettings.pyに以下の項目があります。

ROOT_URLCONF = 'foo.urls'

ここで、fooフォルダ内のurls.pyが指定されています。

ここを変更すればurls.pyのファイル名を変更することができます。

しかし、デフォルトでurls.pyとなっていますので特別な理由がない限り変更する必要はないと思います。

まとめ

ウェブアプリケーションフレームワークであるDjangoにとってURLは大変重要な要素です。
URLに対してViewがページを生成し、それを訪問者が閲覧します。それが結果です。

大きいプロジェクトほどURLの管理、Viewの管理が大変になります。
分割を上手に使って効率よく管理してください。

-入門クラス
-,