Some notes on starting to use Django

Julia Evans

Djangoを使い始めてみてのメモ

こんにちは!私の好きなことの一つに、20年以上前からあるような枯れた技術を、今さら初めて学び始めることがあります。これから自分が直面するであろう問題が、すでに1000回も解決されていて、あとはサクサク作業を進められる。この感覚がとても心地よいのです。

ずっと前から、RailsやDjango、Laravelのような人気のウェブフレームワークを学べたらいいなと思っていました。なかなかきっかけがなく実現できずにいたのですが、数ヶ月前にウェブサイトを作るためにDjangoを学び始め、今のところとても気に入っています。そこで、簡単なメモをいくつか残しておきます!

Railsより魔法が少ない

2020年にRailsを学ぼうとしたことがあります。Railsは素敵でしたし、本当は好きになりたかったのですが(Rubyコミュニティは素晴らしいです!)、数ヶ月放置してから戻ってくると、何をどうすればいいのか思い出すのが大変でした。たとえばroutes.rbresources :topicsと書いてあっても、それだけではtopicsのルートがどこで設定されているのか分かりません。規約を覚えておくか、調べ直す必要があるのです。

私にとって、プロジェクトを数ヶ月、あるいは数年放置しても、また戻ってこられることはとても重要です(私のプロジェクトはどれもそうなのです!)。Djangoの方が物事がより明示的なので、自分には扱いやすく感じます。

私の小さなDjangoプロジェクトでは、(設定ファイル以外では)主に5つのファイルだけを意識すればよい感覚があります。urls.pymodels.pyviews.pyadmin.py、そしてtests.pyです。ほかのもの、たとえばHTMLテンプレートがどこにあるのか知りたくなっても、たいていはこれらのファイルのどこかから明示的に参照されているので見つけられます。

組み込みの管理画面

今回のプロジェクトでは、データベース内のデータを手作業で編集・閲覧するための管理画面が欲しいと思っていました。Djangoにはとても優れた管理画面が標準で備わっていて、少しコードを書くだけでカスタマイズできます。

たとえば、これは私の管理クラスの1つの一部です。一覧画面に表示するフィールド、検索対象にするフィールド、デフォルトの並び順を設定しています。

@admin.register(Zine)
class ZineAdmin(admin.ModelAdmin):
    list_display = ["name", "publication_date", "free", "slug", "image_preview"]
    search_fields = ["name", "slug"]
    readonly_fields = ["image_preview"]
    ordering = ["-publication_date"]

ORMがあると楽しい

以前の私は「ORMなんて必要? SQLを自分で書けばいいじゃないか」と考えていました。しかし今のところDjangoのORMはとても気に入っています。Djangoでは__JOINを表現できるのが面白いです。たとえば次のように書けます。

Zine.objects
    .exclude(product__order__email_hash=email_hash)

このクエリは5つのテーブルにまたがっています。zineszine_productsproductsorder_products、そしてordersです。これを動かすために私がしたのは、「orders」と「products」を結びつけるManyToManyFieldと、「zines」と「products」を結びつけるManyToManyFieldがあることをDjangoに伝えただけでした。そうすることで、Djangoはzinesordersproductsをどうつなげばよいかを理解してくれます。

もちろん、このクエリを自分で書くこともできます。でもproduct__order__email_hashと書く方がずっとタイプ量が少なく、読みやすく感じますし、正直なところ、あのJOIN以外にもいろいろ必要な処理があるクエリを自分で一から組み立てるには、少し時間がかかりそうです。

ORMが生成するクエリのパフォーマンスについては、今のところまったく心配していません。なので今はORMにとてもワクワクしています。もちろん、そのうち不満も出てくるのでしょうが。

自動マイグレーション!

ORMのもう一つの素晴らしいところは、マイグレーションです!

models.pyでフィールドを追加・削除・変更すると、Djangoが自動でmigrations/0006_delete_imageblob.pyのようなマイグレーションスクリプトを生成してくれます。

必要ならそれらのスクリプトを編集することもできるのでしょうが、今のところは生成されたまま実行していて、とてもうまくいっています。まさに魔法のようです。

データモデルを、どういう形にするか考えながら頻繁に変えている今の自分にとって、手軽にマイグレーションできることはとても重要だと感じています。

ドキュメントが好きです

私はドキュメントをまったく読まないという悪い癖がありましたが、これまで読んだDjangoのドキュメントはとても楽しめています。これは偶然ではありません。Jacob Kaplan-Moss氏がPyCon 2011でのトークで、Djangoのドキュメント文化について語っています。

たとえばモデルについてのイントロでは、ORMでよく使う主要なフィールドの設定が分かりやすく一覧されています。

SQLiteを使っています

Postgresの運用でうまくいかず、何が起きているのか理解できないという苦い経験をしてから、小さなウェブサイトはすべてSQLiteで動かすことにしました。その方がずっと調子がよく、VACUUM INTOを実行してできた単一ファイルをコピーするだけでバックアップが取れるのが気に入っています。

本番環境でDjangoとSQLiteを使うために、こちらの手順を参考にしています。

1日に数百件程度の書き込みしか想定していないので、問題ないはずです。これは、もっと書き込みが多いにもかかわらず問題なく動いているMess with DNSと比べてもずっと少ない量です(Mess with DNSでは書き込みが3つの異なるSQLiteデータベースに分散されています)。

メール送信も標準搭載(そのほかもいろいろ)

Djangoは「batteries-included(電池入り)」なところがとても気に入っています。CSRF対策やContent-Security-Policyを使いたいときも、メールを送りたいときも、すべて標準で入っているのです!

たとえば、開発環境ではDjangoが送るメールを実際に送信せずファイルに保存したいと思ったのですが、それも少し設定するだけでできました。

settings/dev.pyに次のように書くだけです。

EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "emails"

そして本番用のメール設定は、settings/production.pyで次のようにしました。

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.whatever.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "xxxx"
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_API_KEY')

これで、ほかにも基本的なウェブサイトの機能が欲しくなったときは、きっとDjangoに最初から簡単なやり方が用意されているだろうと感じられました。

設定ファイルはまだ少し手に余ります

一方で、settings.pyファイルにはまだ少し圧倒されています。Djangoの設定システムは、ファイル内でたくさんのグローバル変数を設定する仕組みですが、もし変数名をタイプミスしたらどうなるのだろう、と少し不安になります。どうやって気づけばいいのでしょうか。たとえばWSGI_APPLICATIONWSGI_APPLICATOIN = "config.wsgi.application"と打ち間違えたらどうなるのだろう、と。

Pythonの言語サーバーがタイプミスを教えてくれるのに慣れてしまっているので、そういったサポートに頼れないのは少し戸惑います。

とりあえずここまで!

これまで、実際のウェブフレームワークをプロジェクトでうまく使えたことがほとんどありませんでした(今のところ、私のウェブサイトは単一のGoバイナリか、静的サイトがほとんどです)。なので、これからどうなるか楽しみです!

まだまだ学ぶことはたくさんあります。Djangoのフォームバリデーションの仕組みや認証システムなどは、まだあまり触れられていません。

ORMにチャンスを与えてみるよう背中を押してくれたMarco Rogers氏に感謝します。

(Mastodonでのコメント機能はまだ実験中です! Mastodonでのコメントはこちらをご覧ください。あなたのお気に入りのDjango機能も教えてください!)

原文は Julia Evans により に公開されました。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。