Djangoフレームワークのセッションの有効期限

Djangoフレームワークのセッションの有効期限について調べる必要があったのでまとめておく。

試したバージョンは、Python 3.9、Django 3.2.4。

ドキュメント

セッションのデフォルト設定

Djangoでプロジェクト生成した際のデフォルトの settings.py では、セッションのアプリケーションは有効になっている。

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',  # セッションアプリケーション
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  # セッションミドルウェア
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

その他にセッションの設定は記載されていないので、デフォルトの設定が使われる。

デフォルトの設定値は、ドキュメントに記載がある。

https://docs.djangoproject.com/ja/3.2/ref/settings/#sessions

設定値の実装を確認したい場合は django.conf.global_settings のコードを読む。

https://github.com/django/django/blob/main/django/conf/global_settings.py

有効期限の扱い

Djangoのセッションは 「最後に更新されたときから有効期限の設定の期間」 が有効期限となる。

既存のセッションのデータがViewなどでの処理で更新された場合、 「有効期限が自動的に延長されていく」 といった仕様になる。

デフォルトの設定では、セッションのデータを参照するだけではセッションは更新されないので、 「最後にアクセスがあったときから~」ではない ことに注意する。

セッションの有効期限を延長する

Djangoのセッションの有効期限を延長したい場合は、セッションの更新を行う。

方法はいくつかある。

  • セッションの値を変更する
    • セッションの値を変更すると、バックエンドでデータが保存、更新されるため、有効期限が延長される
  • セッションの値は変更しないが明示的に更新する
    • request.session.modified = True のようにViewの中で記述しておくと、セッションミドルウェアで変更扱いとして保存、更新される
  • すべてのリクエストでセッションを保存する
    • settings.pyの SESSION_SAVE_EVERY_REQUESTTrue を指定すると、セッションが発行されている場合には、ミドルウェアによりすべてのリクエストでセッションが保存、更新される
    • この方法だと、 「リクエスト毎にセッションの有効期限が延長される」 というのを実現可能
    • SESSION_SAVE_EVERY_REQUEST = True の場合、セッションの保存先のストレージに書き込み処理が多数発生するので、負荷が問題にならないか気をつける必要がある。
class MyView(View):
    def get(self, request):
        ...
        # Viewでセッションの保存を行う場合はどちらかの方法でよい
        request.session["foo"] = "bar"  # セッションの値を更新する例

        request.session.modified = True   # 明示的にセッション保存を指定する例, セッションの値を更新した場合は不要
        ...

セッションが保存されるタイミングはドキュメントにも記載があるので、参考に。

https://docs.djangoproject.com/ja/3.2/topics/http/sessions/#when-sessions-are-saved

セカンドハウスのある生活をしてみた所感

これは pyspa Advent Calendar 2020 の2日目の記事です。

https://adventar.org/calendars/5310

去年の秋から、生活する場所を東京と千葉の2ヶ所にしています。このことについての所感です。

きっかけ

東京の23区外でマンションの部屋を所有して住んでいましたが、プライベートで千葉市に行くことが増えたので、移動が大変だなと思ったのがはじまりです。

東京の家を引き払って引っ越すか考えたのですが、持ち家で色々と便利だったので、東京の持ち家を維持しつつ、千葉のほうは賃貸物件に住むことにしました。

元々、東京の家のローン返済が終わったあと、田舎の方に別荘があっても良いなとは思っていて、「セカンドハウスを持つ感覚とはどういうものか」を経験しておきたかったのもあり、決意しました。

家のエリア、スペック

持ち家のほうは東京の東久留米市です。駅からも距離があり、少し歩く場所ですが、全国チェーンの郊外店舗が多く、買い物には不便しない場所です。去年までは市民農園の畑を借りて野菜を作ったりもしました。畑と住宅街、チェーン系の店舗で構成される典型的な郊外エリアです。

もう一方のセカンドハウスは千葉市です。駅から徒歩10分以内ぐらいで、歓楽街も近く便利なエリアですが、スーパーマーケットは東久留米より少なくて、自炊の機会が多い自分としては、少し遠くても品揃えのよいスーパーマーケットまで足を伸ばしがちになってます。

  • 東久留米市
    • 持ち家、分譲マンション
    • 60平米ぐらい
    • 駅から徒歩20分ぐらい
    • 築10年以内
    • 一軒家や低層マンションが多いエリア
    • 買い物はスーパーマーケット、ホームセンターなどの郊外型店舗が徒歩、自転車圏内に多数あり
    • 飲食店は郊外型のチェーン系店舗が多くある、個人経営の店は少なめ
  • 千葉市
    • 賃貸マンション
    • 40平米ぐらい
    • 駅から徒歩5~10分ぐらい
    • 新築
    • 商業ビルが多いエリア
    • 買い物は駅ビルか、すこし駅から離れる方向の郊外型店舗
    • 飲食店は多い

過ごし方

どちらの家にも半々ぐらい滞在、ということはなく、月ごとに偏りがある感じで使ってます。

池袋、新宿、渋谷など山手線の西側で用事があるときは東京の家に帰ったり、また東京の家から行ったりしてます。

逆に山手線の東側の場合は千葉からのほうが鉄道でのアクセスが良いので、そちらを使ってます。

バイクや車で関東の西側に行くときは、東京の家からのほうが楽なので、東京の家を起点にしてます。

千葉の南部や茨城方面に行くときは、千葉からのほうが楽なので、千葉の家を起点にしてます。

仕事はリモートワークでできるので、家で過ごす時間も長く、気分で場所も変えられるのは良かったなと思いました。

毎日出勤が必要になると、どちらかの家の使用頻度は極端に少なくなるかもしれません。

自炊をする場合、日持ちしない食材を滞在しないほうの家に置けないので、冷凍食材や缶詰など日持ちする食材をストックしてうまく使うようにしています。

東京の家のほうが広いので、ホームパーティをする場合は東京のほうを使っています。

拠点間移動

拠点間の移動は所有してるオートバイ(中型400cc)か、鉄道です。

天気のよい日はオートバイで一般道路か高速道路を使って移動することが多いです。

以下のようにメリット・デメリットどちらもあり、場合によって使い分けています。

  • オートバイ
    • 所要時間は日中だと150分ぐらい、夜中だと80分ぐらい
    • 鉄道に比べて時間の制約が少ない(夜中でも移動できる)
    • 高速道路を使わなければ、電車賃より安く済む
    • 高速道路を使うと移動時間の短縮が大きい
    • 荷物をいくらか積載可能
    • 運転は楽しいけど、運転しないといけないので、移動中に他のことができない
    • 悪天候の場合はつらい
  • 鉄道+バス(徒歩)
    • 所要時間は150~180分ぐらい
    • 時間はかかるけど、移動中に作業したりできる
    • 移動途中に寄り道しやすい
      • バイクだと駐車場を探す必要があるので都心は若干面倒なのです
    • 悪天候でも移動が楽
    • 混雑時間帯だと疲れる
    • 感染症のリスクはバイク移動に比べたら高い

車を持つのが一番良さそうなんですが、維持費がバイクよりも高くなるので、まだ検討中です。

kane

2つの家を維持するので、費用はかかります。両方の家に生活するための家具、家電など一式も必要になります。

個人的にはどちらの家も家賃が高額だと思うエリアは外しています。

現状は住居にかかる費用は収入の2~3割程度におさまっているので、単純に維持するだけであればそこまで負担はないかな、という感じです。

持ち家のほうは住宅ローンが完済済みで、管理積立金と固定資産税の状態であるのが大きいです。

  • 光熱費は両方の家の基本使用料はかかるけど、プランによっては安くできる
  • 駐車場を借りる場合、両方の家で駐車場が欲しくなるので結構かかる
  • 移動にもお金がかかる

まあ、お金はかかりますね。

手入れ

家は使っていると汚れるし、掃除が必要になります。それぞれの家の使用頻度は減りますが、掃除する面積が増えるのでそこそこ大変です。

設備は経年劣化もあるので、なるべく長く使うためにも手入れをしたほうがよいです。

掃除や家の手入れを抵抗なくやれる人でないと、維持がつらくなるかもしれません。私は掃除は嫌いではないですが、時間がかかるので、短縮のために模索しているところです。

面倒な場合はお金で解決する方法もあります。ルンバなどのロボット掃除機も使っていくと良さそう。

その他、言いたいこととか

  • 郵便や荷物の受け取り
    • 基本的にどちらの家にも宅配ボックスがあるので、不在でも受け取れるようにはしています。
    • 対面での受け取りが必須のもの(冷蔵品や本人確認)は不在が数日続いて受け取り失敗することがしばしばあり、不便に感じました。
    • また運送会社の荷物の受け取りサービスは、自宅の住所が1ヶ所しか登録できないものがあり、不便に感じました。

やってみるまではどうなるか予想できなかったことも多かったのですが、セカンドハウスを持つのは中々良い体験だと思いました。

少なくとも活用できるうちは維持しようかなと思ってます。

次は小さい畑をやれる土地もほしいなーとぼんやり考えてます。

とにかく家は維持するにはそれなりにお金や労力が必要になります。

この記事がセカンドハウスを検討してる方の参考になれば幸いです。

Djangoの管理コマンドを上書き(置換え)する

Djangoフレームワークの管理コマンド(manage.py スクリプトのコマンド)をオーバーライド(上書き、置換え)する方法について。

管理コマンドの登録の仕組み

Djangoのドキュメントには、管理コマンドのオーバーライドについての記載があります。

https://docs.djangoproject.com/en/3.1/howto/custom-management-commands/#overriding-commands

Djangoのコマンドの登録メカニズムは、次の通りです。

  • 最初に組込みのコマンドがセットアップされます
  • INSTALLED_APPS のアプリケーションにコマンドがあれば登録します
    • コマンドはアプリケーションのディレクトリ以下に management.commands 以下のモジュールがロードされ、モジュール内のCommandクラスのインスタンスがコマンドとして登録されます
    • モジュール名がコマンド名として使われます(たとえば、 runserver コマンドなら runserver.py)
    • このとき、同じ名前のコマンドがあれば、アプリのコマンドで先に登録されたコマンドを上書き(オーバーライド)します
  • runserver コマンドについては、Django内部で、 django.contrib.staticfiles が組込みの runserver コマンドをオーバーライドしているので気をつける必要があります

既存のコマンドを置き換えてみる

myapp という名前のアプリケーションで、組込みのコマンド migrate を置き換えてみます。

試したバージョンは、 Python 3.8、 Django 3.1.3 です。

myappのディレクトリ構造は以下の通り:

myapp
├── __init__.py
├── admin.py
├── apps.py
├── management
│     ├── __init__.py
│     └── commands
│         ├── __init__.py
│         └── migrate.py
├── migrations
│     └── __init__.py
├── models.py
├── tests.py
└── views.py

myapp.py:

from django.core.management.base import BaseCommand


class Command(BaseCommand):
    def handle(self, *args, **kwargs):
        print("これはmyappのコマンドです")

組込みのコマンドを置き換える場合、組込みのコマンドを継承する必要は必須ではないです。例のように Command という名前のクラスで、 BaseCommand と同等の振る舞いができれば、コマンドとして利用できます。

実行結果

myappsettings.pyINSTALLED_APPS に登録されていると、組込みの migrate コマンドが myapp アプリのコマンドに置き換わります。

$ ./manage.py migrate
これはmyappのコマンドです

『現場で使えるDjango管理サイトのつくり方』を読みました

『現場で使えるDjango管理サイトのつくり方』 著者のakiyokoさんから献本いただきました。

Djangoの管理サイト(Django Admin)にフォーカスした内容の本です。 https://www.amazon.co.jp/dp/B08HVJPRR8/?tag=tokibitossoft-22www.amazon.co.jp

対象の読者

この本はDjangoの管理サイトについての本なので、ある程度Djangoフレームワークの使い方を知っていることが前提となっています。

対象の読者はこんな感じでした。

オススメポイント

  • 管理サイトの基本的な操作方法、使い方の説明がある
  • ModelAdminの拡張方法について丁寧な説明がある
  • 業務の現場で必要になりそうな、よくあるカスタマイズのパターンが紹介されている
    • 管理サイトのラベルを変更する、入力画面のカスタマイズ、バリデーションなど
  • Actionの追加など、拡張方法について丁寧な説明がある
  • 管理サイトのデザインをカスタマイズする方法について丁寧な説明がある

Django管理サイトをカスタマイズする必要がありそうなら、先にこの本に目を通しておくとスムーズに作業できると思うのでオススメです。

私はDjangoの管理サイトを10年以上前からカスタマイズしてきているので構造を把握していますが、もし管理サイトのソースコードをこれから読み込むのであれば、本書を先に読んでおくと楽になるかなーと思いました。

参考

akiyoko.hatenablog.jp

1つのVMインスタンスに複数のアプリを置くような構成を考える

VPSなどの環境で、1つのVMインスタンスにたくさんDjangoアプリをホストしたいなー、という場合の構成を考えたりしてました。

自分で運用しやすい形にまとまったので、メモを残しておきます。

要件

  • 1台のVMで複数種類のDjangoアプリを動かす
  • VMのメモリは2~4GBぐらい(月2000~4000円以下ぐらいのVPS
  • OSはUbuntu
  • DBはMySQL
  • WebサーバーはNginx
  • HTTPSはLet's Encryptを使う
  • MySQLはWorkbenchとかですぐ見れるようにはしておきたい
  • メール送信はSendgridを使う(なのでローカルにSMTPサーバーは不要)

要するにサーバー費用を抑えつつ、アプリをいくつも動かしたい。

負荷が上がった場合は、ミドルウェアを別のものに変えなくても、他のインフラに容易に移設できるような感じにしておきたい。

構成

f:id:nullpobug:20200630224433p:plain
構成図

結局これで一旦落ち着きました。

  • Nginx
    • Docker化するとCertbotが面倒な感じだったので、ホスト側Ubuntuにインストールして利用
    • アプリとの通信はTCP使わずにUnixドメインソケット
  • MySQL
    • アプリごとにDocker化するとメモリ食い過ぎる
    • 運用時にすぐmysqlコマンドやmysqldumpを使いたかったのと、Workbenchでの接続も簡単にしたかったので、ホスト側Ubuntuにインストール
  • アプリ
    • Pythonの環境は分離したかったので、Dockerで動かす
    • アプリごとにdocker-compose.ymlを作って自動起動するように設定してる
    • gunicornはワーカープロセスを1にしてメモリ使用量減らす、代わりにワーカースレッド数を増やす
    • NginxからはUnixドメインソケットで接続するので、Gunicornはソケットファイルにバインド
    • MySQLへの接続はUnixドメインソケット経由
  • cron
    • 定期実行は docker-compose exec でアプリのmanage.pyのコマンドを起動する

以上。小さいDjangoアプリなら1台に10個ぐらいホストしても大丈夫だとおもう。

FlaskのBlueprintのフックポイント(before_requestとbefore_app_request)について

FlaskのBlueprintには before_requestbefore_app_request のフックポイントがあり、どう呼ばれるのか検証していた。

API — Flask Documentation (1.1.x)

ドキュメントを読んでも、実行される順番や条件がわかりづらい。

検証コード

Flaskのバージョンは1.1.2

app.py

from flask import Flask, Blueprint

# Flask==1.1.2
app = Flask(__name__)

bp1 = Blueprint('bp1', __name__)

@bp1.before_app_request
def bp1_before_app_request():
    """before_app_requestはすべてのリクエストでviewの前に呼ばれる
    """
    print("bp1 before_app_request")

@bp1.before_request
def bp1_before_request():
    """before_requestは対象のBlueprintへルーティングされるリクエストでviewの前に呼ばれる
    """
    print("bp1 before_request")

@bp1.route("/bp1")
def bp1_view():
    print("bp1 view")
    return "/bp1"

bp2 = Blueprint('bp2', __name__)

@bp2.before_app_request
def bp2_before_app_request():
    print("bp2 before_app_request")

@bp2.before_request
def bp2_before_request():
    print("bp2 before_request")

@bp2.route("/bp2")
def bp2_view():
    print("bp2 view")
    return "/bp2"


@app.before_request
def app_before_request1():
    """Flask.before_requestはすべてのリクエストでviewの前に呼ばれる
    """
    print("Flask before_request1")

app.register_blueprint(bp1)
app.register_blueprint(bp2)

@app.before_request
def app_before_request2():
    """登録順で実行されるので、こちらはbp2.before_app_requestよりも後になる
    """
    print("Flask before_request2")

app.run(host="0.0.0.0")

実行結果

  • Flask.before_request で登録した関数は、すべてのリクエストでviewの前に呼ばれる
  • Blueprint.before_app_request で登録した関数は、 Flask.before_request と同様にすべてのリクエストでviewの前に呼ばれる
    • Flask.before_request と同じ扱いで、登録した順番で実行される
  • Blueprint.before_request で登録した関数は、対象のBlueprintにルーティングされるリクエストでviewの前に呼ばれる
  • 内部的には、 Flask.before_request_funcs に登録されたものを呼び出しているようだ
$ python app.py
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Flask before_request1  # /bp1へアクセスしたとき
bp1 before_app_request
bp2 before_app_request
Flask before_request2
bp1 before_request
bp1 view
127.0.0.1 - - [14/May/2020 01:34:13] "GET /bp1 HTTP/1.1" 200 -
Flask before_request1  # /bp2へアクセスしたとき
bp1 before_app_request
bp2 before_app_request
Flask before_request2
bp2 before_request
bp2 view
127.0.0.1 - - [14/May/2020 01:34:17] "GET /bp2 HTTP/1.1" 200 -

Pythonの入門者向け動画教材2020年4月開講分

2019年にJMOOCで公開していたPythonの入門者向け動画教材ですが、2020年の4月開講分が受講できるようになりました。

去年は1~4章まででしたが、2章分追加して6章構成になりました。追加部分はオブジェクト指向プログラミングについてです。

JMOOCというオンライン講座の1つとして公開されています。 www.fisdom.org

講座はFisdomというプラットフォームで配信されているため、利用には登録が必要ですが、受講料等はかかりません。無料です。

CMSコミュニケーションズの寺田さんと2人で半分ずつ分担して作成したものになります。

Pythonを学習するための手助けになればいいなと思います。

去年の分