例のクマ情報収集サービスについての忘備録です。
上にリンクを貼ったように、最近話題のクマ目撃情報を収集したり、それをわかりやすく地図上に展開するサービスを作ってみたってハナシをこれまで2回ほど投稿しました。
そのなかで、自分の現在地とクマ目撃報告場所の関係性をわかりやすく表示したいなといろいろ試行錯誤しているうちにぶち当たったのがrequest.META.HTTP_REFERERの使い方でした。
とりあえず今回直面した課題に関する画面群です
(画面1 トップ画面です。「いまいるところ」をクリックすることから始まります)
(画面3 参照したい目撃場所をクリックするとポップアップが表示されます)
(画面4 ポップアップの日付時刻に貼ってあるリンクをクリックすると出てくる画面)
(画面5 「拡大地図を表示」をクリックするとGoogle Mapアプリが表示されるので、Google Mapアプリが提供する機能を使えば自分がいる場所と目撃地との距離、そこへの経路などがわかります)
このやり口を流用すれば、災害時などでの近くの避難所や給水所などの場所やそこへの経路を知ることが出来るよなぁって思ってますので、それを実現するのが次の目標ですね。
今回の解決しようとした問題は自分の場所を示す情報をどう受け渡すか
これまでの地図上に目撃情報をプロットする処理は、データベースからすべて取得しそれを地図上にセットする処理をしてました。その時、地図の中心になるのは最新の目撃情報があった場所としていたので、まずはデータベースから取得したリストの先頭(日付順にソートして取得するので)を地図の中心にセットすれば問題無しだったのです。地図一覧画面から地図個別画面へ遷移したのちに、request.META.HTTP_REFERERを使ってリクエスト元の処理へ戻ってきても、そこではまたイチからデータを取得するのでなんの問題もありrませんでした。それが地図の中心にブラウザから取得した現在地をセットしようとしたとき、共通の地図個別画面からリクエスト元へ戻ってきたときには、現在地データが何処にもなく、それでエラーが発生してしまう状況となったわけです。
原因として判明したのは、現在地と目撃情報一覧を同時に表示する画面(画面2,3)は、初期画面で取得した位置情報を受け取って自分の場所を地図の中央にセットしていたものが、地図個別画面(画面4)からリクエスト元(画面2)に戻ってくる時にはそのパラメタが無いから(セットしてないから)でした。それがわかったのはrequest.META.HTTP_REFERERの機能についてGoogleが提供する対話型AI「Bard」に問いかけた時に返ってきた回答からでした。
request.META.HTTP_REFERERがリクエスト元のURLを覚えているならば、リクエスト先(画面4)では不要であってもリクエスト元(画面2)へ戻ってくる時に必要な情報を予めパラメタとしてセットしておけば、リクエスト元の処理で使えるんじゃないか、と。それに思い至るのに4,5日かかりましたけど、それを思いつく重要な情報はBard先生が教えてくれました。
ありがとう、Bard先生!
(個人的にはEdgeが提供するChatGPTよりもBardのほうが親しみやすい気がする)
request.META.HTTP_REFERER とはなにか?
request.META.HTTP_REFERER はDjangoが提供する「直前に呼ばれたURLを覚えている機能です。
たとえば "bear/index" から "bear/next" に遷移した場合、 "bear/next"で実行される処理で参照するrequest.META.HTTP_REFERERの中身には、"bear/index"がセットされているといった具合です。
なので、共通で利用する機能をつくっておいて、そこから戻るボタンなどでリクエスト元に戻りたい時には、いちいちリクエスト元情報を個別に保存したりしておく必要がないわけです。便利だよな、Django。
今回、機能追加前は対象のデータをデータベースから取得するためのキーを"index?key=12345"などとデータベースのキーとなる情報をパラメタとしてセットしていますが、request.META.HTTP_REFERERはそのパラメタごと一式全部がセットされているので、戻ってきたときにはセットしておいたパラメタを新たに参照することも出来るわけです。
では具体的にどんなコードを書いたのか
これまたBard先生に教えてもらった通りに処理を書いたらうまくいきましたので、そいつをそのままお知らせしますね。
1) リクエスト元で設定したリンクはこんな具合
# 戻ってきたときのことを考えてリンク先では使わないlati,longをセットしています。
<a href='bear/map_d?loc_id=12&lati=123.456&long=123.456>yyyy/mm/dd hh:dd</a>
2) リクエスト先のtemplate(HTML)で「戻る」部分の記述(実際にはbootstrapを使ってボタンを表示させている)
<a href="{{request.META.HTTP_REFERER}}">戻る</a>
3) Python(views.py)で関連の参照されるURL記述部分
# 4)にあるreferer = request.META.get('HTTP_REFERER')で変数refererに
# セットされるのはリクエスト元のテンプレートに渡したリンク先(上記の1)として
# リクエスト元で設定したリンク。具体的には以下のような内容(例)です。
"bear/map_d?loc_id=12&lati=123.456&long=123.456"
4) パラメタ取り出し処理関連部分抜粋
import urllib.parse # 必要ライブラリのimport指定
referer = request.META.get('HTTP_REFERER')
# URLをパースして、パラメタ部分を取得
parsed_url = urllib.parse.urlparse(referer)
params = parsed_url.query
param_list = params.split('&') # パラメタ部分をそれぞれ key=value に分割しています
# ここからパラメタの値のみを取得
lst = []
for param in param_list:
key, value = param.split('=') # keyとvalueを"="で分割するっていう発想がカッコいい
if key != "loc_id": # いらないパラメタは無視する
lst.append(value)
# 緯度経度をそれぞれ変数にセットする。 元パラメタは lati->longの順でセットされて
# いるので決め打ちでオーケー。ただしここで取得したデータはstr()なので、
# Google Mapに渡すときにはfloat()にしておいてください。じゃなきゃエラーに
# なります。
lati = lst[0]
long = lst[1]
処理的にはもっとうまい分離方法があるかもしれないけれど、これで先頭のloc_id関連は無視して、のこりのlatiとlongのvalueのみ取得でき、これでまた現在地の設定ができました。
まとめ
そんなわけでなんとかエラーが出ることもなく、目的通りの動きをしてくれたのですが、最初にこいつのコードを書いた時にはインターネットを何度も検索しながら苦労してたのが、最近はAIサービスの発達で(回答にウソも多いけれど)便利な世の中になったもんだと思いますた。
おしまい