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、一度試してみてください!

aspnet-codegeneratorを実行すると「You must install or update .NET to run this application.」が出たときの対処法

対象バージョン

.NET SDK 9.0.305, Mac OS X 13.7

dotnet --info の表示 .NET SDK: Version: 9.0.305 Commit: 3fc74f3529 Workload version: 9.0.300-manifests.ddedba1d MSBuild version: 17.14.21+8929ca9e3

ランタイム環境: OS Name: Mac OS X OS Version: 13.7 OS Platform: Darwin RID: osx-x64 Base Path: /Users/kimihito/.local/share/mise/installs/dotnet/9.0.305/sdk/9.0.305/

インストール済みの .NET ワークロード: 表示するインストール済みワークロードはありません。 新しいマニフェストをインストールするときに loose manifests を使用するように構成されています。

Host: Version: 9.0.9 Architecture: x64 Commit: 893c2ebbd4

.NET SDKs installed: 9.0.305 [/Users/kimihito/.local/share/mise/installs/dotnet/9.0.305/sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 9.0.9 [/Users/kimihito/.local/share/mise/installs/dotnet/9.0.305/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 9.0.9 [/Users/kimihito/.local/share/mise/installs/dotnet/9.0.305/shared/Microsoft.NETCore.App]

Other architectures found: None

Environment variables: DOTNET_ROOT [/Users/kimihito/.local/share/mise/installs/dotnet/9.0.305]

global.json file: Not found

Learn more: https://aka.ms/dotnet/info

Download .NET: https://aka.ms/dotnet/download

起こったこと

dotnet tool install dotnet-aspnet-codegenerator --global でインストール後に、dotnet aspnet-codegenerator を実行すると以下のエラーが出た

You must install or update .NET to run this application.

App: /Users/kimihito/.dotnet/tools/dotnet-aspnet-codegenerator
Architecture: x64
Framework: 'Microsoft.NETCore.App', version '8.0.0' (x64)
.NET location: /Users/kimihito/.local/share/mise/installs/dotnet/9.0.305

The following frameworks were found:
  9.0.9 at [/Users/kimihito/.local/share/mise/installs/dotnet/9.0.305/shared/Microsoft.NETCore.App]

.NET 9 を入れたはずなのに、.NET 8を要求されているよう。

解決法

dotnet tool install--allow-roll-forward オプションを付与することで解決した。

具体的には

dotnet tool install dotnet-aspnet-codegenerator --allow-roll-forward --global とした

関連Issue

github.com

Django 6.0 に追加される機能からみる、Djangoのコア機能への考え方

Django 6.x Streering Council の一人である Carlton Gibsonさんが Django 6.0 への機能についてブログを書いていました

Looking forward to Django 6.0 • Buttondown

Django 6.0のアルファ版リリースは 2025-09-17 を予定しています

Django 6.0 に追加される機能

ブログ記事には以下の3点が記述されています

機能としてはそれぞれ別ですが、共通していることがあります。 それは「すべてサードパーティライブラリの機能をDjangoの機能として取り込んだ」ことです。(注: 同じインターフェースとは限らない)

これらのパッケージはリリース済みのDjangoのバージョンでも利用ができます。

Djangoの「コア機能」とはなにか

先のブログ記事の作者である、CarltonさんはこのDjangoの「コア機能」への考えについて別の記事を書いています。

Thoughts on Django’s Core • Buttondown

概要として

  • Djangoの良さは、「時間ベースのリリーススケジュール」、「APIの安定性」、「フェローシッププログラム」である

  • 機能が追加されるほど、メンテンス対象の範囲が増えてしまう

  • 新しい機能はまずはサードパーティから始めよう

  • Djangoの機能となると、Djangoのリリーススケジュールに従うことになる。

最後の2点は、Djangoの公式ドキュメントにも同様の記述があります。

Djangoの「変化」

Djangoについてよく耳にする批判の一つが「変化が遅い」「新しい機能が追加されない」というものです。確かに、他のWebフレームワークと比較すると、毎回のリリースで大きな新機能が追加されることは少ないかもしれません。

しかし、これは本当に「変化がない」ということなのでしょうか?Django 6.0の例を見ると、実際には3つもの重要な機能が追加されています。問題は「変化の見せ方」や「変化のアプローチ」が他のフレームワークと根本的に異なることなのです。

これまでCarltonさんのブログ記事の内容からみえるように、Djangoのコアに入ることはある種「枯れているか」を大切にしており、「変化」でなく「置き換え」とすることでDjangoの開発が進められていることがわかります。

まとめ

Django 6.0は「進化の仕方」を私たちに示しています。新しい機能を追加することと、実験的な機能を追加することは同じではありません。Djangoは常に進化していますが、その進化は慎重で、実証的で、長期的な視点に基づいています。 「変化がない」のではなく「変化の仕方が違う」のです。そして、この違いこそが、Djangoが20年にわたって多くの開発者に愛され続けている理由なのかもしれません。

余談ですが JavaScriptライブラリのhtmxにも似たような考えを表明した記事があり、こうした考え方への支持が一定数はあるのかもしれません。

ダラダラと書いてしまいましたが、Django 6.0のリリースが楽しみです!

Djangoの署名付きCookieをテストする方法

この記事のDjangoのバージョンは4.2になります。

署名付きCookieの設定

Djangoで署名付きCookieをセットする際は、set_signed_cookieメソッドを使用します。

from django.http import HttpResponse

def my_view(request):
    response = HttpResponse("Hello, World!")
    response.set_signed_cookie('user_preference', 'dark_mode', salt='my_salt')
    return response

署名付きCookieのテスト方法

署名付きCookieが正しくセットされているかをテストしたい場合、django.core.signing.get_cookie_signerを使用することができます。

実際の値は django.core.signing.get_cookie_signer の返り値である django.core.signing.Signerunsign メソッドを利用します。

テストコードの例

from django.test import TestCase, RequestFactory
from django.core.signing import get_cookie_signer, BadSignature
from myapp.views import my_view

class SignedCookieTestCase(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
    
    def test_signed_cookie_is_set_correctly(self):
        # ビューを呼び出してレスポンスを取得
        request = self.factory.get('/')
        response = my_view(request)
        
        # Cookieが設定されているかを確認
        self.assertIn('user_preference', response.cookies)
        
        # 署名付きCookieの値を検証
        cookie_value = response.cookies['user_preference'].value
        signer = get_cookie_signer(salt='my_salt')
        
        # 署名を検証して元の値を取得
        try:
            original_value = signer.unsign(cookie_value)
            self.assertEqual(original_value, 'dark_mode')
        except BadSignature:
            self.fail("Cookie signature is invalid")

重要なポイント

saltの指定は必須です。get_cookie_signerを使用する際は、set_signed_cookieで使用したのと同じsaltを指定してください。

# set_signed_cookieで使用したsaltと同じものを指定
signer = get_cookie_signer(salt='my_salt')

調べた理由

django.http.HttpRequest.get_signed_cookieユニットテストで利用したかったのですが、うまく使うことができませんでした。

django.http.HttpRequest.get_signed_cookie のコードを見て、上記の実装方法があることを理解し、ユニットテストで利用しています。

まとめ

  • set_signed_cookieで署名付きCookieを設定
  • テスト時はget_cookie_signerを使って署名を検証
  • saltの指定を忘れずに

miseでPythonがインストールできない問題を解決した

小ネタです。

mise install python@3.12.11 を実行したところ以下のエラーが発生

mise ERROR git branch --show-current failed
mise ERROR command ["git", "-C", "/Users/kimihito/Library/Caches/mise/python/pyenv", "-c", "safe.directory=/Users/kimihito/Library/Caches/mise/python/pyenv", "branch", "--show-current"] exited with code 128

「pyenvなんて使ってないけどなぁ」と思いつつmiseのIssueを検索。すると似たような問題に遭遇している人を発見。

github.com

ここのコメント にあったように

$ mise cache clear
$ mise cache prune

を実行した後に再度 mise install python@3.12.11 を実行するとうまくいった。

uvのおかげでdjangoの関連パッケージにプルリクエストを送りやすくなった

最近はDjangoに新しいバージョンが出ると、その対応ができていることを示すプルリクエストを作成しています。高速なPythonパッケージマネージャであるuvを使うことで、このプロセスがとても簡単になりました。

2024年の例として、django-storagesDjango 5.1 対応のプルリクエストを出すときに行った手順を紹介します。 https://github.com/jschneier/django-storages/pull/1444

やったこと

  1. リポジトリをforkする
  2. uv venv を実行して仮想環境を作成(従来の方法より高速で依存関係の解決も正確です)
  3. 仮想環境に入る
  4. django-storagesはtoxを使っている。手元にtoxがないので pip install tox を実行する
  5. Django 5.1をtoxのリストに追加するコミットを作る コミット内容
  6. プルリクエストを作成する プルリクエスト

Python歴が短い自分にとって、uvの高速な環境構築と依存関係の解決は大きな助けになっています。特に uv venv コマンド一つで清潔な環境が素早く作れるので、気軽にオープンソースへの貢献ができます。

Django自体の変化が少ないため、こうした対応をしなくても動くことが多い(実際django-storagesへのプルリクエストではパッケージ内のコードは変えていない)です。 ただ、明示的に対応バージョンを記載しておくことでユーザーにとってはメリットが大きいのかなと思っています。

注意点

こうした貢献は簡単ですが、同時に安易にプルリクエストを乱発することはパッケージのメンテナに負担をかけることになるので、以下のポイントを考慮するようにしています。

  • 本当に必要な変更か確認する(すでに誰かが同様のPRを作成していないか)
  • テストが通ることを確認してからPRを出す
  • ドキュメントの更新が必要な場合は一緒に行う
    • 前述の通りCHANGELOGの変更を忘れていました。反省。
  • メンテナが理解しやすいように変更内容を明確に説明する
    • 今回の例にあるプルリクエストにも説明がありませんでした。こちらも反省。

メンテナの時間と労力を尊重する姿勢も大切にしつつ、貢献していきたいです。

HerokuデプロイでGitエラー「bad line length character: fata」の解決方法

問題の概要

Herokuに新しく作成したアプリケーションにgit pushでデプロイしようとした際に、以下のようなエラーが発生しました

Enumerating objects: 14718, done.
Counting objects: 100% (14718/14718), done.
Delta compression using up to 16 threads
Compressing objects: 100% (6495/6495), done.
fatal: protocol error: bad line length character: fata
Writing objects: 100% (14718/14718), 17.62 MiB | 1007.00 KiB/s, done.
Total 14718 (delta 7769), reused 14137 (delta 7392), pack-reused 0
fatal: the remote end hung up unexpectedly
error: failed to push some refs to 'https://git.heroku.com/example.git'

「bad line length character: fata」というエラーメッセージが表示され、Herokuへのデプロイが失敗します。

原因

この問題は、macOSでGitバージョン2.39.3を使用している場合 に発生します

解決方法

Gitのバージョンを2.39.3より上位のバージョンにアップデートすることで解消しました。

参考情報

help.heroku.com

Herokuサポートページにも同様の情報が記載されていますが、Herokuのアカウントがないと閲覧できなかったのでブログ記事にしました