DjangoのSECRET_KEY_FALLBACKSを使ってSECRET_KEYをローテーションする

Djangoの小ネタです。

SECRET_KEY に入れた値などを変更したい場合、素直に SECRET_KEY の値を差し替えてしまうとユーザのログイン情報やセッションの値がクリアされてしまい、利用するユーザに影響が出てしまいます。

この対応として Django 4.1 から settings.SECRET_KEY_FALLBACKS が導入されました。

https://docs.djangoproject.com/ja/4.1/ref/settings/#std-setting-SECRET_KEY_FALLBACKS

settings.SECRET_KEY_FALLBACKS にはリストが入ります。名前の通りSECRET_KEY を使ったチェックのフォールバックとして機能するため、ユーザへの影響を小さく留めることができます。

なお、本記事の動作確認は Django 6.0.2 で行っています。

使い方

  1. 新しいSECRET_KEYを設定し、古いSECRET_KEYの値をSECRET_KEY_FALLBACKS に導入する
  2. SESSION_COOKIE_AGE(デフォルト: 2週間), PASSWORD_RESET_TIMEOUT(デフォルト: 3日)のうち、長いほうの期間が経過するまで SECRET_KEY_FALLBACKS を維持する
  3. 期間が過ぎたことを確認してから、SECRET_KEY_FALLBACKS を空にする(または削除する)

django.core.signing で有効期限なしの署名を使っている場合、SECRET_KEY_FALLBACKS から削除した時点で BadSignature が発生します。該当箇所がないか事前に確認してください。

注意点

ドキュメントにも記載ありますが、SECRET_KEY_FALLBACKS はリストのため複数の古いキーを保持できます。

検証時はリストの先頭から順にキーを試すため、値が多いほどオーバーヘッドが増加します。古いキーは期間が過ぎ次第、速やかに削除してください。

再考 Choose boring Technology

Shima Tech Hub #15 - connpass でLTするつもりが、急な予定でできなくなったのでその供養として書きます。

Choose boring Technology とは

mcfunley.com

boringtechnology.club

Dan McKinley さんが 2015年に書いた内容です。自身のEtsyでの経験を元に、(主に企業での)技術の選び方についての考えを述べています。

具体的な経験については先のリンクをみてもらうとして、以下の理由でタイトルの通り「退屈な技術(Boring Technology)を選ぼう」ということを指摘しています。

  • イノベーショントークン ≒ 変わったことや新しいこと、難しいことを行うために使えるもの」は3つ(ぐらい少ない)。事業活動にトークンは使ってしまうため技術選択で無駄にしてはいけない

  • 新技術は「知らない問題」( Unkown Unkown )が多すぎる。枯れた技術は失敗モードが既知(Known Unkown)が多い

  • 技術の追加は簡単、運用し続けるのは大変である

似たような話として、「枯れた技術の水平思考」という言葉があります。

dic.pixiv.net

よくあるChoose boring Technologyの技術として挙げられるのはDjangoです。

そのDjangoのカンファレンス(DjangoConUS 2025)のキーノートが「枯れた技術の水平思考」でした。 とても面白いのでよかったら観てみてください!

www.youtube.com

LLMとBoring Technology

さて、ここからが本題です。

2015年に提唱されたこの考え方を、なぜ2025年の今、改めて推したいのか。 答えは、LLMという強力なスキル増幅器が登場したからです。 LLMは、私たちのスキルを増幅してくれます。コードを書くスピードが上がる。知らないことを素早く学べる。デバッグが速くなる。

でも、この増幅器の効果は、すべての技術で同じではありません。技術選択によって、増幅効果が大きく変わります。 そして、Choose Boring Technologyという考え方が、この増幅効果を最大化するんじゃないかと思っています。

ご存知のとおり、LLMは過去の知識が豊富です。特に枯れた技術については、膨大な学習データがあります。

先の例に出したDjangoには20年の知識がインターネットに転がっています。また「やりたいこと」と「書き方」のマッピングが安定しています。

Djangoの枯れ具合や変わらなさとLLMが自身のスキルを増幅する感覚があります。

LLMの情報を発信している simon willsonのブログでも、プログラムにおけるハルシネーションの減らすtipsとして

Chose boring technology. I genuinely find myself picking libraries that have been around for a while partly because that way it’s much more likely that LLMs will be able to use them.

LLMの仕組みを考えれば「当たり前だろ」という感じですが、技術を選ぶ理由として「退屈な技術」が選択されるケースは今後も増えていく(現になっている)と思っています。

LLMという増幅器を手に入れた今、「枯れた技術」の価値を、改めて見直してみてもいいんじゃないでしょうか。

最高!Choose boring Technology

miseで.NET 10がインストールできない

日本時間の2025-11-12に.NET 10がリリースされました 🎉

devblogs.microsoft.com

最近 .NET(というかASP.net)を勉強しています。 バージョン管理はmiseで行っています。

mise.jdx.dev

せっかくなので、「新しい .NET 10 を使いたい!」と思ったところ表題の問題が発生しました。

起きた問題

mise use dotnet@latest を実行しても .NET 10が降ってこなかった。

解決方法

mise use dotnet@10.0.100 と明示的にバージョンを指定することで、 mise ls-remote のリストになくても実行することができました。

なぜか

miseの.NETのインストールスクリプトmise-plugins/mise-dotnet に移譲しています。

# https://github.com/jdx/mise/blob/fc0d8fe5/registry.toml#L1298-L1300
[tools.dotnet]
backends = ["asdf:mise-plugins/mise-dotnet", "vfox:mise-plugins/vfox-dotnet"]
description = ".Net"

https://github.com/mise-plugins/mise-dotnet でインストール可能なバージョンは https://raw.githubusercontent.com/hensou/asdf-dotnet/main/versions.txt にて記載されています。

日本時間の2025-11-12時点では、ここに.NET 10のバージョンである 10.0.100 が記載されていないため、 mise use dotnet@latest しても .NET 10がインストールされません。

ただしmise-plugins/mise-dotnet のコードをみると、指定したバージョンを渡してあげればインストール してくれます。

今回はそれを活用し明示的にバージョンを指定することで解決しました。

.NET 10 のバージョンが 10.0.100 であることは dotnet.microsoft.com

をみて判断しています。

その判断方法は github.com

で見つけました

余談

.NETのインストール対象リストである https://raw.githubusercontent.com/hensou/asdf-dotnet/main/versions.txt自動で更新されている模様 なので、この対処法もすぐに不要になるかと思います。

django-import-exportでリレーションモデルのフィールドが管理画面上のインポートプレビュー画面に表示されない

発生したこと

django-import-export で、対象のモデル( Example ) に関連付けに必要な列( hoge_id )がプレビューで表示されていない問題がありました。

原因

hoge_id は、対象モデルがもつ実際のフィールドではなく、インポート処理でのみ使用するフィールドでした。

かつリレーションもdjango.db.models.ForeignKey で辿れず、より深いものだったので import_export.widgets.ForeignKeyWidget を指定するだけでは実現できませんでした。

行ったこと

1. import_export.fields.Fieldのattributeに一時的な属性を付与

_hoge_id_import という一時的なフィールドをattributesに用意し、CSVインポートの値をそこに格納するようにしました。

from import_export import fields, resources

class ExampleResource(resources.ModelResource):
    hoge_id = fields.Field(
        column_name="hoge_id",
        attribute="_hoge_id_import",
    )

2. プレビュー画面での表示

Meta.fields に含めてプレビュー画面の対象とする

class Meta:
    model = Example
    fields = (
        # ... 他のフィールド
        "hoge_id", 

    )

3. 一時属性の削除

import_instance メソッドをオーバーライドして、保存前に一時属性を削除する

def import_instance(self, instance, row, **kwargs):
    if hasattr(obj, '_hoge_id_import'):
        delattr(obj, '_hoge_id_import')
    return super().import_instance(instance, row, **kwargs)

以上の3つの対応をすることでプレビュー画面に表示することができました。

余談

一時的な属性を付与して削除するやり方がちょっとイケてないなぁと思っています。もっとよいやりかたがあれば知りたいです…

dokku (herokuish buildpack) で GEO/GISのライブラリを入れる

Djangoには django.contrib.gis というパッケージが標準で搭載されています。

docs.djangoproject.com

この django.contrib.gis を使ったWebアプリケーションを作ろうと思ったのですがその環境を作るのにとてつもなくハマってしまいました…

環境

VPSUbuntu 22.04 LTS)にdokkuをインストールしています。今回 dokku apps:create したものにDjangoアプリを載せようとしていました。

ハマったこと

GeoDjangoのドキュメントにあった地理空間ライブラリのインストールに従って、必要なパッケージをインストールしようと思いました。

具体的には https://github.com/heroku/heroku-buildpack-apt.git を buildpackに追加し、 Aptfile を作成。 Aptfile 内に必要なパッケージを記述しました

# Aptfile
binutils 
libproj-dev 
libatlas-base-dev
gdal-bin

github.com

起こったこと

デプロイ中に実行する django manage.py collectstatic --no-input が失敗しデプロイできなくなりました

       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/db/backends/postgis/adapter.py", line 6, in <module>
       from django.contrib.gis.geos import GEOSGeometry
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/geos/__init__.py", line 6, in <module>
       from .collections import (  # NOQA
       ...<4 lines>...
       )
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/geos/collections.py", line 7, in <module>
       from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/geos/geometry.py", line 9, in <module>
       from django.contrib.gis import gdal
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/gdal/__init__.py", line 29, in <module>
       from django.contrib.gis.gdal.datasource import DataSource
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/gdal/datasource.py", line 40, in <module>
       from django.contrib.gis.gdal.driver import Driver
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/gdal/driver.py", line 5, in <module>
       from django.contrib.gis.gdal.prototypes import ds as capi
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/gdal/prototypes/ds.py", line 10, in <module>
       from django.contrib.gis.gdal.libgdal import lgdal
       File "/app/.heroku/python/lib/python3.13/site-packages/django/contrib/gis/gdal/libgdal.py", line 71, in <module>
       lgdal = CDLL(lib_path)
       File "/app/.heroku/python/lib/python3.13/ctypes/__init__.py", line 390, in __init__
       self._handle = _dlopen(self._name, mode)
       ~~~~~~~^^^^^^^^^^^^^^^^^^
       OSError: libblas.so.3: cannot open shared object file: No such file or directory

OSError: libblas.so.3: cannot open shared object file: No such file or directory が出てしまっています

解決法

https://github.com/heroku/heroku-buildpack-apt.git のbuildpackでなく、

Herokuが公式に提供している heroku-geo-buildpack を導入することで解決します

github.com

起きた理由

エラーでググると同じ症状が

github.com

どうも、 heroku-buildpack-apt においてインストールしたパッケージがサブディレクトリに配置されていると検出してくれないようです。

対応のプルリクエストも出ているのですが放置されています。

解決策に提示した https://github.com/heroku/heroku-geo-buildpack ではGEO/GISで必要なライブラリのインストールだけでなくきちんとパスの処理も実行されている ことから問題なく動作します。

django-fastdevを使ってDjangoの開発体験を向上させよう

github.com

django-fastdev というPyPIパッケージが良かったので紹介します。

django-fastdev とは

パッケージの名前のとおり、Djangoの開発を手助けするライブラリです。

主に開発時のデバッグを楽にするための機能がいろいろあります。

使い方は INSTALLED_APPSdjango_fastdev を追加するだけです(リンク元のREADMEを参照してください)。

ここでは代表的なdjango-fastdevの機能を紹介します

django-fastdevの機能

テンプレートで未定義の変数があれば例外を出す

Django Templateなどで未定義の変数を参照してしまうことがあります。通常であれば None として扱われるため変数の表示がないことに気付けないときもあります。

django-fastdevではそのような変数がテンプレートで呼び出されていた場合、 FastDevVariableDoesNotExist という例外が発出されます。

# templates/index.html などで views.py で参照していない no_variable_nameがある
...
{{ no_variable_name }}
...

なお、 default_if_nonedefault など、テンプレートの変数がNoneだった場合にフォールバックするテンプレートタグが呼び出されていれば(django-fastdevのデフォルトの設定では)例外はでません。

定義のないblock を呼び出した場合に例外を出す

Django Templateの変数と同様に {% block %} の呼び出し先が存在しない場合、通常であればエラーにはなりません。 django-fastdevを利用することで例外を発出します。

# templates/index.html などで存在しない no_block_nameを読んでしまう
...
{% block no_block_name %}
...

参照のないURLを呼び出したエラー画面に、存在する urlpattern 表示してくれる

{% url "no-url" %} など urls.py で定義したurlpatternsに存在しないURLを呼び出した場合、通常はReverse for 'no-url' not found. 'no-url' is not a valid view function or pattern name. といった例外だけが発出されます。

django-fastdevを利用していると、この情報に「定義済みのurlpatternsのリスト」を出してくれます

( These names exist: index と書いているのがわかります)

他にも紹介していない機能などがありますが、主に開発時のタイプミスに気づきやすくなるものがいくつかあります。

かゆいところに手が届く django-fastdev、一度試してみてください!