概要と結論
タイトルの通りですが、しばらくハマっていたのでメモを残しておきます。
結論としては、set_expiry(0)
とした時には有効期限=0となるのではなく、ブラウザを閉じた瞬間にSessionが破棄されます。もちろん挙動としては有効期限を0にしても同じなのですが、例えばTestCaseを書く時、sessionの有効期限を取得するget_expiry_age()
を呼ぶと困ります。0ではなく、デフォルトの有効期限(1209600 = 2週間の秒表記)が戻ってくるからです。
前提知識: Djangoのsession
Django公式ドキュメント: セッションの使い方
こちらのリンクに書かれている通り、デフォルトの設定でDjangoはCookieにSessionIDを保存し、それを利用してサーバ側のデータベースに、訪問者ごとのデータを保存しています。
Requestオブジェクトにはsession属性があり、viewなどからこちらにデータを保存or読み取りできます。↑のリンクの この辺 をみると、具体的な使い方が書いてあります。
また、ユーザのログイン情報もsessionを利用して保持しています。
Sessionの有効期限
sessionには有効期限が存在していて、デフォルトでは2週間データを保持します。これはsettingsのSESSION_COOKIE_AGE変数によって変更できます。公式ドキュメントのこの部分に記載があります。
また、viewなどからsessionの有効期限を設定することができます。上述のRequestオブジェクトのsession属性から、set_expiry
メソッドを呼べばいいです。
本題: set_expiry(0)の意味
set_expiry(0)の挙動はDjango公式ドキュメント: セッションの使い方: set_expiry こちらに記載されています。以下に引用します。
もし value が 0 ならば、ユーザのセッションクッキーは、ユーザがウェブブラウザを閉じた時に破棄されます。
なので、以下のようになります。
# 1(0以外)をセットした場合
>>>request.session.set_expiry(1)
>>>rquest.session.get_expiry_age() # 有効期限を取得するメソッド
1 # 設定した値が取得できる
>>>request.session.get_expire_at_browser_close() # ブラウザを閉じた時にセッションクッキーを破棄するかを調べるメソッド。
False # 破棄しない
# 0をセットした場合
>>>request.session.set_expiry(0)
>>>rquest.session.get_expiry_age()
1209600 # 0ではなく、デフォルト値が表示される!(これはsettings.SESSION_COOKIE_AGE由来)
>>>request.session.get_expire_at_browser_close()
True # Trueを返すようになる(破棄する)
直感的にはset_expiry(0)
の例で、get_expiry_age()
を呼べば0が帰ってくるように思います。しかし実際は0をセットした時、有効期限は変更されません。
代わりと言っては何ですが、「ブラウザを閉じた時にセッションクッキーを破棄するか?」を調べることができます。
それがget_expire_at_browse_close()
メソッドです。
セッションの有効期限(get_expiry_ageで取得できる値)は、あくまでブラウザを閉じてもセッションクッキーを残すという条件でのみ意味を成します。
余談
ソースコードを見てみましょう。(やり方が正しいかは不明...)
まずDjangoのrootディレクトリを調べて、cdコマンドで移動しておきます。
$ python -c 'import django; print(django.__path__)'
['/Users/kyu/Document/venv/lib/python3.7/site-packages/django']
# 移動
$cd /Users/kyu/Document/venv/lib/python3.7/site-packages/django
先ほどの公式ドキュメントの ここ をみると、set_expiryやget_expiry_ageは backends.base.SessionBase
というクラスに定義されているようです。なので、以下のようにして探します。
# rはリカーシブです。
# lはファイル名だけ表示するオプションです。つけないと、本文も隣に出てきて見づらいです....
$ grep 'SessionBase' -rl . # 現在の場所(.)の下からSessionBaseがテキスト内部に含まれるfileを探します。
....省略.....
# 見つかりました!
./contrib/sessions/backends/base.py
開いて中を見てみると....
# まずはset_expiryを見てみます。
258 def set_expiry(self, value):
# ....省略.............
# 最終的に、_session_expiry に保存しているようです。
282 ¦ self['_session_expiry'] = value
次はgetメソッドです。
212 def get_expiry_age(self, **kwargs):
# ............省略..................
225 ¦ try:
226 ¦ ¦ expiry = kwargs['expiry']
227 ¦ except KeyError:
228 ¦ ¦ expiry = self.get('_session_expiry')
# ............省略..................
230 ¦ if not expiry: # Checks both None and 0 cases
231 ¦ ¦ return self.get_session_cookie_age()
232 ¦ if not isinstance(expiry, datetime):
233 ¦ ¦ return expiry
kwargsを指定しなければ、先ほど保存された_session_expiry
由来の値がexpiry変数に束縛されているはずです。
興味深いのは230~231行目です。コメントも書かれていますが、0はFalse扱いなので、set_expiryで0をセットした場合にはget_session_cookie_age()
の値が帰ります。
これが本記事で取り上げた挙動の原因です。(expiryの値は返らない)
正直、ゼロをセットしたのならゼロを返せばいいような気もしますが、ドキュメントにもソースコードのコメントにもこのように書かれているからには何か意味があるのでしょう....(私にはわかりませんでした)