知識の枝

"All is well"

Djangoでよく見る「request」の話

約54日前 2021年9月3日23:44
デジタル
Django

改訂履歴


2021/9/3 投稿

1. HttpRequestとは


関数ベースビューを作成する際、関数の第一引数に「request」を入れますよね。

この「request」という引数には「HttpRequestオブジェクト」というものが渡されます。


HttpRequestというのは、私達がブラウザでWEBページにアクセスする際にWEBサーバーへ渡されるリクエストのことです。

このリクエストには下記のような情報が入っています。

リクエスト情報の例
・リクエストメソッド(GETやPOSTなど)
・どのページへアクセスするか
・どんな手段でアクセスしているか(ユーザーエージェント)
・クエリストリング

などなど

サーバーはこのリクエストを受け取ると、「HttpResponse」という形で私達にWEBページを見せてくれます。


Djangoではこの「HttpRequestオブジェクト」をビューの引数に渡します。
views.py
def IndexView(request):  # requestはHttpRequestオブジェクト
# do something
# return HttpResponse


どのビューに渡すかはurls.pyで定義します。

urls.pyの中でリクエストのパス(URL)によって呼び出すビューを切り替えています。
urls.py
urlpatterns = [
path('URL部分', views.IndexView, name='index'),
]
この役割を「URLディスパッチャ」と呼び、URLによってビューを切り替える作業を「ルーティング」と言います。

■ディスパッチャ(dispatcher)
派遣する人、通信指令係

■ルーティング(routing)
経路の決定


ビューの中ではレンダリングに使用するhtmlテンプレートを選択したり、モデルを読み込んだりと色々なことができます。

また、引数としてビューに渡したHttpRequestを活用することも可能です。


「request」を活用する例としては、

リクエストメソッド(GETやPOST)の種類に応じて処理を変えるといった場面が挙げられます。

views.py
# ビューの中
if request.method == "POST": # もしリクエストメソッドがPOSTなら
# do something


HttpRequestオブジェクトには様々な情報が入っています。

上記の例で出た「.method」には「どんなリクエストメソッドか」という情報が入っています。

他にもメタ情報(request.META)というものには下記のデータが辞書形式で入っています。

request.METAの中身

CONTENT_LENGTH -- リクエスト本文の (文字列としての) 長さです。
CONTENT_TYPE -- リクエスト本文の MIME タイプです。
HTTP_ACCEPT -- レスポンスに対して受け入れ可能なコンテンツのタイプです。
HTTP_ACCEPT_ENCODING -- レスポンスに対して受け入れ可能なエンコーディングです。
HTTP_ACCEPT_LANGUAGE -- レスポンスに対して受け入れ可能な言語です。
HTTP_HOST -- クライアントによって送信された HTTP Host ヘッダです。
HTTP_REFERER -- (存在する場合) リファラページです。
HTTP_USER_AGENT -- クライアントのユーザエージェント文字列です。
QUERY_STRING -- クエリ文字列で、単一の (未解析の) 文字列です。
REMOTE_ADDR -- クライアントの IP アドレスです。
REMOTE_HOST -- クライアントのホスト名です。
REMOTE_USER -- (存在する場合) Web サーバによって認証されたユーザです。
REQUEST_METHOD -- "GET" や "POST" といったです。
SERVER_NAME -- サーバのホスト名です。
SERVER_PORT -- (文字列としての) サーバのポートです。

Django公式ドキュメント

request.METAはPythonの辞書型ですので、各項目の中身を下記のように取り出し可能です。
request.META["HTTP_USER_AGENT"]
>> Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
"KEY"を指定して"VALUE"を得ます。


ちなみに「request」はcontextという形で指定しなくてもテンプレート変数として利用可能です。

htmlテンプレートで下記のように書けばメタ情報をWEBページ上に表示させることが可能です。
template.html
{% for key, value in request.META.items %}
<p>{{key}}:{{value}}</p>
{% endfor %}


requestの正体が分かってきたところで、よく使う「データのやりとり」について次項で解説します。



2. リクエストとデータのやりとり


データのやりとりと言っても、ここではクライアント → サーバーの一方通行のデータ送信について解説します。

リクエストにデータをのせてサーバーへ送る方法はリクエストメソッドによって異なります。


2.1 - GETリクエストの場合


GETリクエストでサーバーにデータを送る場合は「クエリストリング」を使います。

こちらの記事で少し触れました。
Query String Parameter


クエリストリングとはURLの末尾に付与される"KEY"と"VALUE"の関係を持つ文字列です。

このページのURLの末尾に「?test=abc&hoge=foo」と付け、アクセスしてみましょう。
https://chuna.tech/detail/90/?test=abc&hoge=foo

表示されるページは同じです。

でもURLはしっかり「https://chuna.tech/detail/90/?test=abc&hoge=foo」になっていますね。


この場合のクエリストリングは2つです。
KEYVALUE
testabc
hogefoo

この2つのクエリストリングがGETリクエストに乗っかってサーバーへ渡されています。


実際にどんなリクエストがされたか確認してみましょう。

Chromeブラウザは右クリックから「検証」という機能を使うことができます。

リクエストの情報を見てみると、クエリストリングとしてちゃんとデータが渡されていることが確認できました。




さて、無事サーバーにデータが渡されました。

せっかくだからそのデータを使って何かしてみたくないですか?


そこで出てくるのが「HttpRequestオブジェクト」です。

request(HttpRequestオブジェクト)にはクエリストリングの情報も保存されています。

ビューの中で取り出してみたいと思います。

views.py
def IndexView(request):
# requestからクエリストリングをそのまま取り出す
meta_querystring = request.META["QUERY_STRING"] # >> "test=abc&hoge=foo"

# クエリストリングを辞書ライクなオブジェクトとして取得
dict_like = request.GET

# 辞書から"KEY"を指定して"VALUE"を取り出す
value_01 = dict_like["test"] # >> "abc"
value_02 = dict_like["hoge"] # >> "foo"

# 辞書から取り出す別の方法(こちらを推奨)
value_03 = dict_like.get("test") # >> "abc"
value_04 = dict_like.get("hoge") # >> "foo"

# 辞書から全ての"KEY"と"VALUE"を取り出す
for key, value in dict_like.items():
print(key + ":" + value)
# >> test:abc
# >> hoge:foo
「request」から様々な方法でデータを取得することが可能です。

ここで1点「request.GET」について。

突然でてきましたので、Djangoの公式ドキュメントから説明を抜粋します。

HttpRequest.GET
渡された HTTP GET パラメータを含む、ディクショナリライクのオブジェクトです。後述の QueryDict のドキュメントを参照してください。


class QueryDict
HttpRequest オブジェクト内では、GET と POST 属性は django.http.QueryDict のインスタンスです。これは、同一のキーに対する複数の値を扱うためにカスタマイズされた、辞書型に似たクラスです。これが必要なのは、いくつかの HTML (特に <select multiple>) が同一キーで複数の値を渡すからです。



簡単に言うと、
GETリクエストの場合は「クエリストリング」の内容を「request.GET」というインスタンスを使って”辞書っぽく”使えるようにしています。

POSTリクエストの場合は「リクエストボディ」の内容を「request.POST」というインスタンスを使って”辞書っぽく”使えるようにしています。



「辞書っぽく」という言葉が引っかかりますね。

ドキュメントによるとQueryDictは、

同一のキーに対する複数の値を扱うためにカスタマイズ

された特殊な辞書とのこと。

これはどういうことかと言うと、下記のようなクエリストリングがあり得るという話。

「?test=abc&hoge=foo&test=fuga」

"test"というキーが重複しています。

純粋なPythonの辞書型の場合、1つのキーに対し1つの値しか登録できません。

Djangoでは上記クエリストリングのように重複が発生する場合がある為、”辞書ライクなオブジェクト”を使うことで対策しています。


上記のように重複したキーを持った辞書からデータを取り出してみましょう。
value = request.GET.get("test")  # "test"というキーで取り出す
print(value)
>> "fuga"
"abc"と"fuga"の2つの値があるはずなのに、片方しか取得できていませんね。

DjangoのQueryDictに対してget()メソッドを使うと、その"KEY"に対して最後に登録された"VALUE"しか返してくれません。

ですので、request.GET.get("test")は"fuga"になってしまいます。


値を全て取り出したい場合は別のメソッドを使用します。
values = request.GET.getlist("test")
print(values)
>> ['abc', 'fuga'] # リストで取得
getlist()というメソッドを使うと、"KEY"が重複している場合の"VALUE"全てをリスト形式で取得できます。



2.2 - POSTリクエストの場合


データの保存される場所は異なりますが、扱い方は同じです。

POSTリクエストが送信される場合の例としては、WEBページのフォームに何か情報を入力して「送信ボタン」を押すシチュエーションが挙げられます。

フォームに入力されたデータはリクエストボディという場所に入った状態でWEBサーバーに渡されます。


Djangoは便利なので、データの保存場所がGETと異なっていても同じような方法で取得することができます。
views.py
def IndexView(request):
# リクエストボディを辞書ライクなオブジェクトとして取得
dict_like = request.POST

# 辞書から"KEY"を指定して"VALUE"を取り出す
post_value = dict_like.get("KEY") # >> "VALUE"
POSTの場合の"KEY"にあたるのは<input>タグの「name」属性です。

上記はname属性が「KEY」のフォームに「VALUE」と入力して送信ボタンを押した場合に相当します。
template.html
<input type="text" name="KEY">


もうなんとなく分かると思いますが、name属性が重複しているフォームが複数ある場合はgetlist()メソッドを使いましょう。

get()メソッドでは一番最後のフォームの値しか取得できません。


3. まとめ


簡単ではありますがDjangoのrequestについて解説させて頂きました。

HttpRequestからデータを取得する方法はAjaxでよく使います。

取り出し方が分からなくなったらもう一度見に来て下さい。

お疲れ様でした。