URLの表記がよくわからない
Djangoを使い始めたばかりの人たちの中には、Djangoで開発したアプリのURLの表記がよくわからないという人たちが少なからずいるのではないだろうか。
例えば 'http://localhost/article/1111/' のURLでブラウザで表示させているページを編集する場合、「articleディレクトリに入っている1111ディレクトリに含まれるHTMLファイルを編集すればいいのかな?」なんていう目星をつけたところ、そのようなディレクトリは存在せず、編集したいページの基になっているファイルにアクセスできないという状況に陥ってしまうことはないだろうか。自分1人で1から開発をしている分にはこのようなことはほぼ起きないと思うが、Djangoを用いた共同開発において、他の人が作成したファイルを編集したいときに起こりがちなことではないだろうか。
特に筆者のように、Webアプリケーションフレームワークに触れたことがほとんどなく、1からHTMLファイルやCSSファイルなどをエディタなどを用いて編集をして作成し、レンタルサーバーなどにアップロードして公開していたような人たちが陥りがちであると思われる。
そこで本記事では、DjangoのURLの表記や仕組みを理解できるように、そもそもDjangoにおいてはどのような仕組みでページを表示させているのかを、筆者自身の勉強のためにも解説していく。
Django上で動作しているページが表示される仕組み
今回は以下のディレクトリ構成になっているDjangoのプロジェクトを用いて、トップページが表示されるまでの過程を説明する。
myproject
│ Docker-compose.yml
│ Dockerfile
│ manage.py
│ requirements.txt
│
├─app
│ │ admin.py
│ │ apps.py
│ │ models.py
│ │ tests.py
│ │ urls.py
│ │ views.py
│ │ __init__.py
│ │
│ ├─migrations
│ │ │ __init__.py
│ │ │
│ │ └─__pycache__
│ │ __init__.cpython-312.pyc
│ │
│ └─__pycache__
│ admin.cpython-312.pyc
│ apps.cpython-312.pyc
│ models.cpython-312.pyc
│ urls.cpython-312.pyc
│ views.cpython-312.pyc
│ __init__.cpython-312.pyc
│
├─project
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ │ __init__.py
│ │
│ └─__pycache__
│ settings.cpython-312.pyc
│ urls.cpython-312.pyc
│ wsgi.cpython-312.pyc
│ __init__.cpython-312.pyc
│
├─static
│ ├─css
│ │ style.css
│ │
│ └─images
└─templates
index.html
実行環境 | Docker 24.0.6 |
---|---|
ホスト名 | localhost |
Djangoバージョン | 4.2.7 |
プロジェクト名 | project |
アプリ名 | app |
今回はデータベースやフォームなどは実装されておらず、トップページにCSSで装飾された文字が表示されているだけの、極めてシンプルなWebサイトをブラウザに返すようなプロジェクトである。
1. リクエストに含まれるURLからアプリケーションを識別
今回は以下のようなURLをリクエストとして送った場合を考える。
2. project/urls.pyが呼び出される
Djangoではリクエストが来たらプロジェクト名が名前になっているディレクトリ(今回は "project" )の直下にある "urls.py" が呼び出される。このディレクトリにはプロジェクト全体に関わるファイルが格納されており、この "urls.py" もそのうちの一つである。
"urls.py" には、プロジェクト全体に関わるルーティング内容が記述されている。ここでいうルーティングとは、わかりやすく言えばリクエスト内容に応じて、どの処理を紐づけさせるかを定める作業のことである。
今回のプロジェクトにおいて、 "project/urls.py" の中身は以下の通りである。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('app/', include('app.urls')),
]
urlpatternsにはURLのマッチングパターンがリスト形式で格納されており、上から順にホスト名以下のURLとpath関数の第一引数をパターンマーチングさせている。
今回はリクエストとして、 "http://localhost:8000/app/" を送ったため、ホスト名以下の "app/" に着目すると、リストの1番目の要素である(最初の要素を0番目とする)
path('app/', include('app.urls')),
がマッチングした。
ここではpath関数と呼ばれるものが使われており、第一引数にはURL(ホスト名以下のパス)、第二引数には第一引数で指定したURLに対応させるビューを指定する。ビューとはフォームやデータベースとのデータのやり取り、テンプレートへのHTML生成の指示などといった、いわば司令塔のような役割を有しているものである。上記のコードにおいては第二引数にinclude関数が用いられており、これは今回の場合は、appアプリケーションのurls.py(app/urls.py)に処理を一旦任せるという指示を出していることを意味している("app/urls.py" でビューが呼び出されている)。
3. app/urls.pyが呼び出される
先ほどinclude関数によって "app/urls.py" が呼び出された。"app/urls.py" の中身は以下の通りである。
from django.urls import path
from .views import IndexView
urlpatterns = [
path("", IndexView.as_view()),
]
ここでもurlpatternsリストとpath関数が登場している。役割としては先程と同様であり、今回の場合は第一引数が "" となっていることから、すべてのURLがここにマッチングをするということを意味している。第二引数では "IndexView"と呼ばれるviewが呼び出されている。このIndexViewは "app/urls.py" の上の方の行に
from .views import IndexView
という形でimportされている。もう少し具体的に説明すると、同じディレクトリにあるviews.pyの中で定義されているビューであるIndexViewがimportされているということになる。
4. app/views.pyが呼び出される
"app/views.py" の中身は以下のようになっている。
from django.views.generic import TemplateView
class IndexView(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["message"] = "hello, world!"
return context
IndexViewはクラスベースビューと呼ばれるもので定義している。これはビューの定義方法の1つであり、他には関数ベースビューと呼ばれるものがある。両者の細かい違いに関しては本記事では述べないが、簡単に言ってしまえば、クラスベースビューの方はDgangoが用意しているビューを継承することで、ビューとしての基本機能をDjangoに肩代わりさせることができるため、関数ベースビューと比較してシンプルなコードで定義できるというメリットがある。
今回は "TemplateView" というDjangoがあらかじめ用意しているクラスベースビューを継承して、"IndexView" を定義している。これはテンプレートの表示に特化したクラスベースビューである。
IndexViewでは、 "template_name" に描画するテンプレート("index.html")を指定している。そして "get_context_data" メソッドでは、テンプレートに渡すデータを辞書型で作成している(今回はキー名 "message" とそれに対応する値の "hello, world!"が格納されている)。
5. templates/index.html
"templates/index.html" の中身は以下のようになっている。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
<title>app</title>
</head>
<body>
<h1>{{message}}</h1>
</body>
</html>
templetesにはHTMLの雛形が格納されている。今回の場合は先ほど "app/views.py" で作成した辞書型のデータをこの雛形に渡している。そして<h1>で囲われている {{message}} によって、messageキーに対応する値である "hello, world!" が埋め込まれる。またモデルやフォームを用いていた場合、そこから取得されたデータもビューがテンプレートに渡すこともある。
6. index.htmlを返す
以上のような処理を経て、"index.html" がブラウザに返される。
全体図
その他
先ほどの例では、ビューで定義した辞書型のデータ("hello, world!")をブラウザに表示させるだけの、極めてシンプルなWebページの例であったが、データベースやフォームを用いたサイトを構築する際にも、Djangoの基本的な仕組みは同じである。
例えばデータベース上で "bmi" という名前のテーブルに格納されているデータをすべて表示させたいときには、ビューで以下のように定義することで、モデル(データベースに対してSQL文を発行する役割を有しているもの)を介して、データベースからデータを取得して、そのデータをテンプレートの1つである "index.html" に渡している。
from django.views.generic import ListView
from .models import bmi
class bmiListView(Listview):
template_name = 'index.html'
model = bmi
上記のコードでは "Listview" という指定したテーブルのデータを取得して表示させるクラスベースビューを継承したことで、 "bmi" テーブルのすべてのデータを "index.html" に渡している。
終わりに
本記事では "hello, world!"を表示させるだけの極めてシンプルなサイトを用いて、Djangoの基本的な仕組みを解説した。
ページが表示されるまでの基本的な仕組みを理解することで、DjangoのURLの表記が理解できたのではないだろうか。
DjangoのURLの表記方法はたくさんの種類があるので、今回学んだことをベースに他の表記も学んでもらえれば幸いである。
参考文献
大高隆 . 動かして学ぶ!Python Django開発入門 第2版 . 翔泳社, 2023.