Quantcast
Channel: akiyoko blog
Viewing all 160 articles
Browse latest View live

技術書典6 で『現場で使える Django REST Framework の薄い本』を頒布します

$
0
0

akiyoko です。

本日いよいよ「技術書典6」が開催されますね。私は今回も「あきよこブログ」としてサークル参加します。これで3回連続3回目のサークル参加になります。

f:id:akiyoko:20190413231539p:plain:w400


過去2回で2冊の「Django の薄い本」を出してきましたが、今回の新刊も Django 関連本です。『現場で使える Django REST Framework の薄い本』というタイトルからお分かりの通り、「Django REST Framework(通称「DRF」)」に関する技術同人誌です。

Django REST Framework(DRF)って何?

「Django」は Python で Webアプリケーションを作成するためのフレームワークで、Webアプリケーションを作成する際に必要となるコンポーネント(部品)や API を多数提供しています。Django は多数の機能を提供しているものの、REST API バックエンド(REST API を提供する Webアプリケーション)を構築する際のさまざまなニーズに応えるには少し機能が足りず、いろいろと自前で実装しなければいけません。

そこで登場したのが、「Django REST Framework」(以降「DRF」と表記)です。DRF は、REST API バックエンドを構築することに特化したサードパーティ製の Django パッケージで、REST API を提供する際に必要となる機能や便利な機能を補完・拡充してくれる拡張パックのような役割を果たします。実際のプロジェクトでは、Webアプリケーションのベースとなる機能は Django を利用し、REST API に特化した機能は DRF を利用する、といった使い方をします。


f:id:akiyoko:20190413224931p:plain:w500

DRF 本を書いた理由

かく言う私も現在仕事で「DRF」を使っており、仕事の現場においては「素の Django」よりも「DRF」の方が使われているようにも感じています。しかしながら、DRF に関する日本語情報は Django よりも圧倒的に不足しています。日本語書籍については現在どこを探しても見つかりません。商業誌を待っていてもあと何年かは出ないだろうから、もし技術書典6に当選したら30ページくらいの本当に薄い本を書いてみようかなと思い立ったのが昨年末。これが地獄の始まりでした。。

それからどうなったか?

技術書典4から3回連続で技術同人誌を頒布していますが、今回が一番辛かったです。昨年末あたりから執筆のための調査を始めていて実質的な執筆は2月から開始したのですが、あれよあれよという間に30ページが50ページになり、70ページになり、最終的には表紙を含めて128ページの「薄くない本」に仕上がってしまったのでした。

毎回執筆内容を Git で管理していて今回からコミットコメントに執筆時間を加えることにしてみたのですが、今回の合計執筆時間は333時間になりました(会場価格は1,000円なので私の作業1時間あたり「3円」で買えてしまうということですね・・)。内容には満足しているものの、3月は生活のすべてを投げ打って執筆に没頭してしまいました。ご迷惑をお掛けした関係各所の皆さま、本当に申し訳ありませんでした。最後に、家族のサポートがなければこの本は世に出ていなかったということも付け加えさせてください。

どんな内容?

Django REST Framework(DRF)を現場で使う際に必要となる基礎知識についてまとめた技術同人誌で、本文124ページ、まるまる「Django REST Framework」に関する本です。なお、DRF は Django をベースにしている部分があるので Django についての解説は要所要所で書いているものの、Python についての解説は一切ありませんのでご注意ください。

前半は DRF の概要や全体像、コンポーネントごとの仕組みや使い方、セキュリティや認証などの基礎知識を座学で進めていきます。後半はチュートリアルおよび発展的な Tips という構成になっています。

後半のチュートリアルでは、Django も Django REST Framework も何にも分からないという方でもちょっとした SPA が作れるように、Django REST Framework と Vue.js を使った SPA のサンプルプロジェクトを2つ、簡単なものと少し難しめなものを用意しました。これを手本に写経すれば、REST API を使った Webアプリケーションの仕組みが何となく掴めるでしょう。


レビュアーとして、くろのて勉強会を主催をしている @crohacoさんにもご協力いただいたのですが、@crohaco さんからお墨付きをいただけました。DRF の勉強会ではお世話になりました。そしてレビューありがとうございました。

そのほか、Django 系イベントでいつも会う @kaizumakiさんや u1 さんにもレビューをしていただきました。ありがとうございました!

対象読者

対象読者としては、

  • Django は何となく分かるが、Django REST Framework は初めてという方
  • Django REST Framework のまとまった日本語情報を手に入れたい方
  • Django REST Framework + Vue.js で SPA を構築したい方
  • 現場で本格的に Django REST Framework を使いたい方

を想定しています。

最低限必要の知識としては「Django の仕組みが何となく理解できていること」です。Django 公式チュートリアルDjango Girls チュートリアルをひと通り終えたくらいであれば、何とか理解できるレベルになっているかと思います。拙著『現場で使える Django の教科書《基礎編》』を読み終えたくらいの知識があれば万全ですが、『現場で使える Django の教科書《実践編》』レベルの知識は不要でしょう。


現場で使える Django の教科書《基礎編》

現場で使える Django の教科書《基礎編》

現場で使える Django の教科書《実践編》

現場で使える Django の教科書《実践編》


目次

気になる目次は ↓ のようになっています。


はじめに
第1章: Django REST Framework(DRF)の概要
第2章: モデル・シリアライザ・ビュー・URLconf の基本的な使い方
第3章: DRF とセキュリティ
第4章: DRF と認証
第5章: チュートリアル その1: DRF + CDN 版 Vue.js で Cookie 認証付き SPA をスピード構築
第6章: チュートリアル その2: DRF + Vue CLI 3 で JWT 認証付き SPA を本格構築
第7章: 現場で使える Tips 集
おわりに

f:id:akiyoko:20190413231404p:plain:w300


頒布本情報

当日のお品書きを作りました。
お釣りがなるべく出ないように考慮した「会場特別価格」です。

f:id:akiyoko:20190413231539p:plain:w400



あと、既刊の『現場で使える Django の教科書《基礎編》』『現場で使える Django の教科書《実践編》』も持っていきます。

f:id:akiyoko:20190413231654p:plain:w400

f:id:akiyoko:20190413231728p:plain:w400

これらの既刊については、Amazon や BOOTH で電子版とペーパー版が絶賛販売中です。


◆ 現場で使える Django の教科書《基礎編》 - Amazon(電子版・ペーパー版)

現場で使える Django の教科書《基礎編》

現場で使える Django の教科書《基礎編》

◆ 現場で使える Django の教科書《実践編》 - Amazon(電子版)

現場で使える Django の教科書《実践編》

現場で使える Django の教科書《実践編》

◆ 現場で使える Django の教科書《実践編》 - BOOTH(ペーパー版)
現場で使える Django の教科書《実践編》【紙の本】(技術書典5バージョン) - あきよこブログ(akiyoko - BOOTH

頒布場所

頒布場所は「か63」の「あきよこブログ」です。
見本誌を置いてますので、立ち読みだけでもお気軽にどうぞ。

f:id:akiyoko:20190413232620p:plain

池袋サンシャインシティ2F、4/14(日) 11時オープンです。今回は、会場2時間は有料入場券(1,000円)が必要になっています。それでも午前中は混雑すると思いますのでご注意ください。

なお勝手ながら、当日13時までの有料入場者特典として、弊ブースにて『現場で使えるDjangoの教科書《実践編》』(紙の本)を会場特別価格からさらに500円引きさせていただきます(1,500円→1,000円)。(後払いアプリだと対応できないかもしれないので)現金払いのみでの割引とさせてください。



最後に

ウチの奥さんに「そんなに寝てないと死ぬよ?」と何度も言われながら、ようやく今週の火曜にすべり込み入稿できました。表紙がモノクロだったので標準価格からのアップは免れましたが、カラーだったらアウトというギリギリっぷりでした。そんな感じで今回の執筆で気力体力ともに尽き果てました。今日の技術書典が終わったら昇天しているかもしれません。。


DjangoCongress JP 2019 で「現場で使える Django のセキュリティ対策」というタイトルで登壇してきました

$
0
0

akiyoko です。

昨日の DjangoCongress JP 2019(Day 1 カンファレンスデー)で「現場で使える Django のセキュリティ対策」というタイトルで登壇してきました。その報告です。


発表資料は こちらにアップしています。


発表の動機

仕事の現場でもっと Django を使ってほしいという想いから、この発表を思い立ちました。Django 普及の突破口のひとつとして、まずはセキュリティの懸念を取り払うことが重要なのでは?と考えたのです。

Python が企業でも幅広く使われるようになったことや書籍が充実し始めたことで、日本でもようやく Django が注目されるようになりました。4月に待望のバージョン2系の LTS がリリースされ、ますます現場で Django が盛り上がることが予想されますが、導入検討の際にネックになるもののひとつに「セキュリティ」への懸念があります。私は現在、ある Django プロジェクトの立ち上げに携わっているのですが、そこで実際によく言われたのが「Django ってセキュリティ的にどうなん?」でした。日本では(特に SIer 界隈では)Django がまだあまり知られていないので、そのような疑問が出てくるのは当然といえば当然かもしれません。

一方の開発者からすると、フルスタック(全部入り)の Django にはデフォルトでセキュリティ対策も含まれていますが、そのあたりの理解が中途半端だと、リリースした Webアプリケーションが悪意ある攻撃者の格好の餌食になってしまいかねません。そうならないためにも、Django のセキュリティ事情については開発前から(もちろん開発中でも)きちんと把握しておきたいところです。

そこで今回、IPA(情報処理推進機構)が注意喚起情報として公表している「安全なウェブサイトの作り方」という冊子にまとめられた代表的な脆弱性の概要と、それらの脆弱性に対して Django では具体的にどのような対策をしているのかについて解説することにしました。これによって発注側企業においてはセキュリティの懸念が払拭され、一方の開発側企業においてはセキュリティ施策のヒントとして活用されることを期待しています。

対象者

対象者は、Django の導入を検討している方、および Django を使った Webアプリケーション開発者を想定しています。なお、Django 初級者の方にも十分理解できる内容になっていると思います。

Webアプリケーションの基本的な知識(特に Cookie を使ったセッション管理や HTTPヘッダなどの知識)があれば、セキュリティの知識についてはほぼゼロ(用語を何となく聞いたことがあるレベル)でも問題ありません。CSRF などの重要な、そして重要な割にあまりよく理解されていないものについては、「CSRF(しーさーふ)って何?」というレベルの人にも分かるように基本から解説しています。

ポイント

メインの話題として

① Webアプリケーションのセキュリティ対策ってそもそも必要なの?
② 「これだけは絶対やっとけ」的な基準はある?
③ フルスタック(全部入り)の Django を使えばセキュリティ対策もまるっと万事解決?

の三点について話しました。
詳細についてはスライドをご覧ください。

発表の準備

昨年の LTでド緊張して失敗した苦い思い出があるので、今回はまず、心構えから入りました。事前に読んだのは、たまたま見つけた オリラジ中田さんの講演会ログです。要約すると、プレゼンでは「カンペを読まない」「言い訳しない」「練習をサボらない」の3つのタブーがあるということです。中でも、人前で練習をすることが「練習」でそれ以外は「練習」ではないというのはまさにその通りだと思い、リハーサルをなるべく多くこなそうと考えていました。

発表の内容については CFP を出した頃に目次レベルまで固めていたのですが、実際に発表資料を準備し始めたのは仕事の関係もあって GW の終盤からとなりました。しかしこれはちょっと遅すぎました。資料が完成したのは本番5日前。そこから2回、会社の Django 経験者やコンサルタントのメンバーの前、そして現プロジェクトメンバーの前でのリハーサルをおこないました。メンバーからのフィードバック、そして自身の反省から、資料がブラッシュアップされていき、話した方がよいこと・話さなくてよいことが明確になり、想定問答も明らかになって私にとっては良いこと尽くめでした。もっとリハーサルをしたかったのですが、何度も集まってもらうのもメンバーの負担になるのと、直前にフィードバックをもらっても反映できない、余計に混乱してしまうと感じたので、これが限界だったのかも知れません。あとはブツブツ独り言を繰り返し、本番の流れをイメージしていきました。


フィードバックの中で、「図を指差しで説明するよりもレーザーポインターでやった方がいい」と言われて急遽買ったのがこれ。パワポのページ送りもできます。本番前日の夕方に届きました。しかしこれが、当日あのような事件を引き起こすことになろうとは。。


あと、GW 中盤に引いた風邪がずっと長引いていたので体調を整えるために前日夜にマッサージに行ったのですが、強かったのか余計に首を痛めてしまいました。


発表はどうだった?

「発表前にやることリスト」を作っていたので、それを淡々とこなすことで緊張はギリギリのところで抑えられていました。

しかし、プレゼン開始直後に事件が起きました。前日の動作チェックでは問題なかったレーザーポインターがページ送りができなくなり、直前に使用を諦めました。これで緊張のコップから水が少し溢れてしまいました。。


f:id:akiyoko:20190519130935p:plain:w180

この事件がきっかけとなって冒頭の10分はガチガチでした。途中からあきらめの境地に至ってから割と話せるようになったと思いますが、そこで何とか踏み止まれたのも事前のリハーサルのおかげです。あれが無ければもうボロボロの結果になったに違いありません。協力してくださった皆さん、本当にありがとうございました。



質問

想定よりも時間がかかってしまいましたが、発表は42分頃に終わりました。そこで出た質問について補足します。

Q.{% csrf_token %}タグを入れ忘れてしまうのを防ぐ方法はないのか?

POST リクエストを送信する form 要素に {% csrf_token %}タグを入れ忘れると、403 エラーが出ます。しかしながら、CsrfViewMiddleware を無効にするなり @csrf_exempt デコレータを利用したりすると、CSRF 検証はされずに脆弱性が露呈してしまいます。会場では、設定ファイルのセキュリティチェックをしてくれる「python manage.py check --deploy」を実行すれば CsrfViewMiddleware が無効化されていることをチェックできるかも知れないと回答しましたが、それを改めて検証してみました。

実際に CsrfViewMiddleware を「MIDDLEWARE」からコメントアウトして check コマンドを実行すると、期待通りに CsrfViewMiddleware が無効化されているという警告が出ました。

(venv) $ python manage.py check --deploy
...(略)...
?: (security.W003) You don't appear to be using Django's built-in cross-site request forgery protection via the middleware ('django.middleware.csrf.CsrfViewMiddleware' is not in your MIDDLEWARE). Enabling the middleware is the safest approach to ensure you don't leave any holes.


いい感じですね。


反響

いろいろと反響をいただきましたが、一番驚いたのがこれです。


あの徳丸さんにTweetしてもろた!😲マジか!

徳丸さんは、私の発表でも紹介した Webアプリのセキュリティ対策の金字塔「徳丸本」こと『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版』の著者です。現場でも必読書になっています。



あと夕方頃に気付いたのがこちら。Speaker Deck のスライド資料が、はてなブックマークのホットエントリーに入っていたみたいです。


最後に

今年の DjangoCongress JP 2019(Day 1 カンファレンスデー)はセッションもパーティーも大盛況で、大御所イベントのような安定感がありました。これも、ずっと前からこの日のために動いてくれていたスタッフの皆さんの準備、運営、そして情熱のおかげです。本当にありがとうございました。昨年も参加させていただき、そして今年はセッション発表の機会をいただいて、このような素晴らしい場で発表側に回れたことは大変光栄に感じています。おかげさまで昨年のリベンジもできました。またたくさんの方にセッションを聴きに来ていただき、嬉しくそして心強かったです。機会があれば来年もチャレンジしてみたいと思います。




おまけ

じゃんけん大会で一番人気だった『現場で使える Django REST Framework の薄い本』は、BOOTH というオンラインショップから購入可能です(紙の本のみ)。

akiyoko.booth.pm


DjangoCongress に来るくらいの人はもうほとんど持ってる or 知ってるという噂もありますが(笑)、『現場で使える Django の教科書』シリーズ(基礎編・実践編)も BOOTH で購入できます。

akiyoko.booth.pm


『現場で使える Django の教科書』シリーズ(基礎編・実践編)は、Amazon でも販売しています(電子版もあり)。

現場で使える Django の教科書《基礎編》

現場で使える Django の教科書《基礎編》

現場で使える Django の教科書《実践編》

現場で使える Django の教科書《実践編》

DRF を使うなら必読!『現場で使える Django REST Framework の教科書』を技術書典7 で頒布します

$
0
0

9月22日に開催される 技術書典7に、「あきよこブログ」として 4回目のサークル参加をします。

みなさん安心してください、今回も Django 本ですよ 😁


新刊は『現場で使える Django REST Framework の教科書』です。


f:id:akiyoko:20190908041824p:plain:w400


「Django REST Framework(通称 DRF)」にピンと来ない方がいるかもしれませんが、「Django」の上に乗っかった、API を作ることに特化したライブラリです。つまり DRF は、Python で REST API を作るためのフレームワークです。SPA(シングルページアプリケーション)やスマホアプリのバックエンドとして利用されることが多いです。Django も DRF も日本ではあまり知られていませんが、実は海外では結構有名です。

どんな内容?

技術書典6 で頒布した前作『現場で使える Django REST Framework の薄い本』にかなり大幅な加筆・改訂を加えて、「Django の教科書」シリーズとしてリニューアルしました。前作の124ページに対して、今作は204ページと大ボリュームの仕上がりになっています。

今のところ、同人誌を含めて 日本でオンリーワンの「Django REST Framework」の日本語書籍になっています。

内容は、Django REST Framework を現場で使う際に必要となる基礎知識と発展的な Tips をまとめたものになっています。なお、Django REST Framework は Django をベースにしている部分があるので、Django についての解説に紙面を割いているところもあります。

前半は DRF の概要や全体像、コンポーネントごとの仕組みや使い方、セキュリティや認証などの基礎知識を座学で進めていきます。後半はチュートリアルおよび発展的な Tips という構成になっています。

後半のチュートリアルでは、Django も Django REST Framework も何にも分からないという方でもちょっとした SPA が作れるように、Django REST Framework と Vue.js を使った SPA のサンプルプロジェクトを2つ、簡単なものと少し難しめなものを用意しました。これを手本に写経すれば、REST API を使った Webアプリケーションの仕組みが何となく掴めるでしょう。

目次

f:id:akiyoko:20190907201801p:plain:w500
f:id:akiyoko:20190908101706p:plain:w500

対象読者

対象読者としては、

  • Django は何となく分かるが、Django REST Framework は初めてという方
  • Django REST Framework のまとまった日本語情報を手に入れたい方
  • Django REST Framework + Vue.js で SPA を構築したい方
  • 現場で本格的に Django REST Framework を使いたい方

を想定しています。

最低限必要の知識としては「Django の仕組みが何となく理解できていること」です。Django 公式チュートリアルDjango Girls チュートリアルをひと通り終えたくらいであれば、何とか理解できるレベルになっているかと思います。拙著『現場で使える Django の教科書《基礎編》』を読み終えたくらいの知識があれば万全ですが、『現場で使える Django の教科書《実践編》』レベルの知識は不要でしょう。


前作の薄い本と何が違うの?

ざっと挙げるとこんな違いがあります。

  • 本文が204ページに大幅増(前作の124ページから1.6倍!)
  • DRF のコンポーネントの解説が充実
  • ユニットテストの章を新設
  • Tips(発展編)のトピック数が 5 ⇒ 13 に(ページ数は 11p ⇒ 31p)
  • 各種ライブラリは最新バージョンに対応

一番の特徴は、コンポーネントの解説を充実させたことです。「モデル・シリアライザ・ビュー・URLconf」で1つの章になっていたのを、それぞれ独立した章として新設しました。ページ数では、モデル 1p ⇒ 18p、シリアライザ 8p ⇒ 18p、ビュー 14p ⇒ 25p、URLconf 5p ⇒ 8p とそれぞれ大幅に増えています。特に、シリアライザは DRF の中でも大きな部分を占めるので、図を増やしてイチから分かりやすい解説を目指しました。

あとは、Django 初心者に少し優しくなりました。特に、前作ではモデルについての説明を「Django と同じ。以上!」としていたのを、きちんと章立てをして解説しています。内容は『現場で使える Django の教科書《基礎編》』からのほぼ抜粋になりますが、スッキリとまとめ直した上で DRF 向けに修正を加えています。

全体としては、加筆を120ページほどおこなっています。



そして、「そこまで言われても、前作の薄い本買っちゃったしなぁ。どうしよう・・」と悩んでいるあなたに朗報です。

前作『~の薄い本』を技術書典7に持ってきていただいた方、先着50名に、なんとワンコイン(500円)で新刊を買えるサービスを実施します!


f:id:akiyoko:20190908122859p:plain:w400

ただし、お一人様1冊限りでお願いします(薄い本にはスタンプを押させていただきますのでご了承ください)。


頒布本情報

既刊の『現場で使える Django の教科書《基礎編》』『現場で使える Django の教科書《実践編》』を含めて、「Django の教科書」シリーズが3冊揃っています。

なおいつものように「会場特別価格」です。


f:id:akiyoko:20190917121916p:plain:w300


f:id:akiyoko:20190917121947p:plain:w300


実は今回の技術書典に合わせて《実践編》を1年振りにリニューアルして、Django 2.2 対応等をおこないました。


これらの既刊については、Amazon や BOOTH で電子版とペーパー版が販売中です。


◆ 現場で使える Django の教科書《基礎編》 - Amazon(電子版・ペーパー版)

現場で使える Django の教科書《基礎編》

現場で使える Django の教科書《基礎編》

◆ 現場で使える Django の教科書《基礎編》 - BOOTH(ペーパー版)
現場で使える Django の教科書《基礎編》【紙の本】 - あきよこブログ(akiyoko blog) - BOOTH


◆ 現場で使える Django の教科書《実践編》 - Amazon(電子版)

現場で使える Django の教科書《実践編》

現場で使える Django の教科書《実践編》

◆ 現場で使える Django の教科書《実践編》 - BOOTH(ペーパー版)
現場で使える Django の教科書《実践編》【紙の本】(技術書典6バージョン) - あきよこブログ(akiyoko - BOOTH



頒布場所

日時は 9/22(日)11時から17時まで、頒布場所は池袋サンシャインシティ3F 展示ホール「お74C」の「あきよこブログ」です。

見本誌を置いてますので、立ち読みだけでもお気軽にどうぞ。

f:id:akiyoko:20190917122645p:plain



最後に

余裕をもって始めたはずの執筆ですが、急遽、《実践編》の改訂と増版をしたのもあり、今回もギリギリの入稿になってしまいました。出すものは全部出した感があるので、自分なりにスッキリしています。技術書典当日は晴れやかに迎えられそうです!

PyCharm で Django の開発をするなら絶対やっておくべき便利な設定

$
0
0

この投稿は 「Django Advent Calendar 2019 - Qiita」 6日目の記事です。

Python 開発の IDE には「PyCharm」を激推ししている akiyoko です。
推しの理由は、以下の点が非常に有用だと感じているからです(JetBrains のステマじゃないですよん 😅)。

  • インストールしたそのままの状態で快適に使える
  • venv」(仮想環境を作成するためのモジュール)や「pip」(パッケージ管理ツール)のコマンドの使い方が分からなくても、画面からポチポチと操作できる
  • コードジャンプ機能と自動整形機能を使うことで開発効率が著しく向上する
  • デバッグ実行を使うことでコード解析が捗る


中でもデバッグ実行が便利ですよね。ライブラリの中もどんどん潜ってデバッグ実行できるので、障害解析やコードの理解が格段に捗ります。


Django の開発をする際にももちろん PyCharm を愛用しています。プライベートでは有償版の PyCharm Professional Edition を利用していますが、仕事では無償版の PyCharm CE(Community Edition)を利用しています。



PyCharm の開発元 JetBrains の 昨年の調査によると、Webアプリ開発をするときは 30〜40%くらいの方が PyCharm(有償版・無償版を合わせて)を利用しているとのこと。私の周りのつよつよ Pythonista の方でも PyCharm を愛用している人は多いです。

この記事を書くにあたって事前に統合開発環境(IDE)の利用状況をアンケートしてみたのですが、

PyCharm の利用率が 30〜40%なのは JetBrains の調査と概ね合致します。
それにしても、VS Code の勢いがすごいですね!
Webアプリ開発ではフロントエンドまわりの面倒も見なくてはいけないことが多々あるので、JS まわりのサポートが弱い PyCharm CE(無償版)よりも、VS Code が選ばれるという状況があるのかなと感じました。皆さま、アンケートにご協力ありがとうございました 🙇‍♂️



前置きが長くなりましたが、ここから本題です。

有償版・無償版を問わず、PyCharm を利用して Django プロジェクトの開発をするなら「これだけはぜひ設定しておいてほしい」という便利な設定があります。


それは、「runserver」の実行(Run / Debug)設定です。



この設定をすることで、

  • ワンクリックで runserver のデバッグ実行が可能
  • runserver の実行で PyCharm のターミナルツールウィンドウを専有しない

というメリットが得られます。



runserver の実行(Run / Debug)設定

では、その設定方法です。*1


Django プロジェクトのひな型を作成する際に「startproject」コマンドを実行すると、管理コマンドを実行するための「manage.py」が自動生成されますよね。

その「manage.py」を右クリックし、「Create 'manage' ...」を選択します。なお、すでにこのプロジェクトで Run / Debug 設定をしたことがある場合は「manage.py」の右クリック時にこの選択肢が表示されないのでご注意ください(その場合は「Run」>「Edit Configurations...」メニューから Run / Debug の設定をおこなってください)。

f:id:akiyoko:20191203080923p:plain

Run / Debug 設定画面が表示されるので、「Parameters」に「runserver」と入力して、「OK」をクリックします。この際、「Name」には、分かりやすいようにたとえば「runserver」と入力しておけばよいでしょう。

f:id:akiyoko:20191203080931p:plain:w500

Run / Debug 設定を済ませると、ナビゲーションバーの右側に「runserver」との表示がされます。

f:id:akiyoko:20191203080957p:plain

「runserver」の右側の虫アイコンをクリックすればデバッグ実行が開始されます。

f:id:akiyoko:20191203081003p:plain:w400


デバッグ実行を開始すると、デバッグツールウィンドウ(Debug タブ)が起動してコンソールにログが出力されます。コードの途中にブレークポイントを置いておけば、コードが実行されたときにそこで中断されて変数などがデバッグができるようになります。

f:id:akiyoko:20191204003907p:plain


デバッグツールウィンドウの使い方については、公式ヘルプの「デバッグツールウィンドウ - ヘルプ | PyCharm」を参照してください。



いつもなら runserver を実行するのに

> python manage.py runserver

というコマンドをターミナルツールウィンドウ(Terminal タブ)を使って実行するところを、デバッグ実行はそれとは別のデバッグツールウィンドウ(Debug タブ)上で動作するため、ターミナルのウィンドウを専有することがありません。このため、makemigration、migrate、createsuperuser などの管理コマンドを実行するために、わざわざターミナル上で実行している runserver を停止して、その後 runserver を再実行する・・なんてことをする手間が不要になるのです。


これは便利ですよね〜 😚




おまけ ①

ここで、便利な裏技(?)をひとつ紹介します。

デバッグ実行中にブレークポイントでコードの実行を止めた状態ではデバッグツールウィンドウに「Debugger(デバッガ)」と「Console(コンソール)」という2つのタブが表示されます。ここで、「Console」タブをクリックして、Python ロゴのアイコン(Show Python Prompt)をクリックしてみてください。


f:id:akiyoko:20191205033219p:plain:w550


すると、任意の Python コードが、止めたコード内で実行できるようになります。
ハイ、便利〜 😆


おまけ ②

runserver 実行時にオプションを指定したい場合がありますよね。

そんな場合は、ナビゲーションバー右側の「runserver」をクリックし、「Edit Configurations...」を選択して Run / Debug の設定画面を開きます(「Run」>「Edit Configurations...」メニューからでも開くことができます)。

f:id:akiyoko:20191205041253p:plain:w500


「Parameters」に設定している「runserver」のところを「runserver --settings xxx」などと書き換えることで、実行時のオプションが指定可能です。

f:id:akiyoko:20191205041641p:plain

また、「Environment variables」から「DJANGO_SETTINGS_MODULE」などの環境変数を設定することも可能です。


おまけ ③

runserver の他によく利用する Django の管理コマンドとして、「test」(ユニットテストの実行)があります。

今回紹介した runserver の実行設定と同じようにワンクリックで Django プロジェクトのユニットテストを実行したい場合は、次のように「runserver」の設定をコピーするのが簡単です。

コピーしたら、「Parameters」を「test」に書き換えます(「Name」も適宜「test」などと書き換えます)。

f:id:akiyoko:20191205041944p:plain


あとは、「Run」アイコンか「Debug」アイコンをクリックするだけで Django プロジェクトのユニットテストが実行できるようになります。

f:id:akiyoko:20191205043241p:plain:w500



おまけ ④

ここまでくると、Coverage.py を利用したカバレッジもワンクリックで実行したいですよね。

こんな感じで Run / Debug 設定をすれば OK です。

f:id:akiyoko:20191205042003p:plain

f:id:akiyoko:20191205042020p:plain

ポイントは、「Script path」のところを「Module name」に変更してから、「coverage」と手入力するところですかね(仮想環境下にインストールされた coverage の絶対パスを「Script path」に指定してもよいですが)。もちろん、pip で coverage をインストールしておいてくださいね。

ちなみに、有償版の PyCharm Professional では、PyCharm に内蔵された独自のカバレッジ機能を使ってワンクリックでカバレッジを取得することができます。*2



まとめ

PyCharm で Django の開発をするなら、runserver の実行(Run / Debug)設定をやっておきましょう。

  1. 「manage.py」を右クリックして「Create 'manage' ...」を選択
  2. Run / Debug 設定画面で「Parameters」に「runserver」と入力して「OK」

これだけです。ほんの5分足らず(もしかしたら1分くらいで??)で設定は終わります。

runserver のデバッグ実行をポチッとワンクリック(ショートカットもあります)でスタートできる便利さを味わうと、もう PyCharm から離れられませんよ〜 😙


Enjoy Django & PyCharm !! 😉


f:id:akiyoko:20191206074253p:plain




明日は、nogisshさんの「Django Advent Calendar 2019 - Qiita」7日目の記事です。よろしくお願いします。



宣伝

Django の本を3冊書きました。Django 開発のお供にどうぞ。

現場で使える Django の教科書《基礎編》

Amazon(電子版/ペーパー版)

現場で使える Django の教科書《基礎編》

現場で使える Django の教科書《基礎編》

BOOTH(ペーパー版)

現場で使える Django の教科書《実践編》

Amazon(電子版)

現場で使える Django の教科書《実践編》

現場で使える Django の教科書《実践編》

BOOTH(ペーパー版)

現場で使える Django REST Framework の教科書

Amazon(電子版)

BOOTH(ペーパー版)

*1:PyCharm のバージョンは Community Edition(無償版)の「2019.3」(現時点での最新バージョン)です。

*2:ビルトイン開発者用ツール - 機能 | PyCharm

2019年の akiyoko blog 振り返り

$
0
0

明けましておめでとうございます。
毎年恒例となっている昨年のブログの振り返りから、今年もスタートです。


ちなみに 2018年の振り返りはこんな感じでした。

<過去記事>
akiyoko.hatenablog.jp


2019年の akiyoko blog 振り返り

昨年一年間にアップした記事の本数は 6本で、昨年の 12本から半減しました。2018年に引き続いてずっと Django 本の執筆をしていたからですが、アウトプットの全体量が減ったというわけではありません。カンファレンスやオープンセミナーでの登壇や新人教育、同人誌の頒布など、むしろアウトプットは激増していると思います。

ちなみに、記事の更新頻度が減ったからか、ブログ全体のアクセス数も一昨年の 3/4 ほどに減ってしまっていました。


ブログ記事ごとのアクセス数ランキング(akiyoko blog 2019年)

記事ごとのアクセス数ランキングです。2019年内のアクセス数(*1)ランキング上位 30本をリストアップしています。

なお昨年中に書いた記事 6本中、30位以内に入ったのは 1本のみでした。 *2


#昨年比タイトル作成日ポイント
1「プロジェクトマネージャ試験」に一発合格するための三か条 - akiyoko blog2014/10/2674.5
2PyCharm のオレオレ最強設定 - akiyoko blog2017/03/1063.4
3「一対一」「一対多」「多対多」のリレーションを分かりやすく説明する - akiyoko blog2016/07/3155.5
4初学者・初級者向け Django の学習ロードマップ - akiyoko blog2018/12/0139.6
5pandas.DataFrame の列の抽出(射影)および行の抽出(選択)方法まとめ - akiyoko blog2017/04/0334.7
6無料版 PyCharm で Django 開発環境を構築するまでの手順(「現場で使える 基礎 Django」本の補講その2) - akiyoko blog2018/06/1730.6
7IPA「情報セキュリティマネジメント試験」に一夜漬けで合格するためのたった二つの勉強法 - akiyoko blog2016/11/1719.8
8AppStore 登録前の iOSアプリを Ad-Hoc で配布してインストールする方法 - akiyoko blog2014/08/2318.6
9PyCharm で Django の開発をするなら絶対やっておくべき便利な設定 - akiyoko blog2019/12/0616.3
10まだ CSV の文字化けで消耗してるの?(Excel で直接開いても文字化けしない CSVファイルを Python3 で作成するスマートな方法) - akiyoko blog2017/12/0915.0
11Mac の MySQL クライアントに「Sequel Pro」を使っているなら PostgreSQL クライアントは「PSequel」がオススメ - akiyoko blog2016/07/2914.7
12Apple Developer Program の有効期限が切れてしまったときの対処方法 - akiyoko blog2015/11/1313.4
13Django ORM の select_related, prefetch_related の挙動を詳しく調べてみた - akiyoko blog2016/08/0311.7
14matplotlib のグラフに日本語を表示する方法(文字化け対応) - akiyoko blog2017/04/1111.3
15Git で コミットを無かったことにする方法 (git revert の使い方) - akiyoko blog2014/08/2111.2
16Video.js を使って HLS形式の動画をストリーミング再生する - akiyoko blog2015/08/1111.0
17ゼロからはじめる Amazon QuickSight(AWS でお手軽データ分析 その3/3) - akiyoko blog2017/03/1510.4
18GitHub の Wiki に画像を貼り付ける一番簡単な方法(Wiki リポジトリを clone しないバージョン) - akiyoko blog2016/08/309.5
19Python で MagicMock を使う - akiyoko blog2015/01/048.7
20Ansible 初心者なら、まずは Ansible Galaxy から始めてみよう - akiyoko blog2015/12/068.4
21Python でリストのソートまとめ - akiyoko blog2014/09/268.3
22Pythonで単回帰直線 - akiyoko blog2013/06/167.8
23Pandas の DataFrame の基本的な使い方 - akiyoko blog2017/03/277.0
24まだ Moodle で消耗してるの? オープンソースの Python製 LMS「RELATE」が圧倒的にカスタマイズしやくてヤバイぞ! - akiyoko blog2017/12/197.0
25ゼロからはじめる Django で ECサイト構築(その1:ECパッケージの選定) - akiyoko blog2016/05/246.7
26JavaScript で配列の添え字に文字列やマイナス値を使ったときの挙動 - akiyoko blog2015/03/216.6
27Django ORM の SQL を出力する方法まとめ - akiyoko blog2016/08/046.1
28PyCharm のデータベースツールが最強。ER図も簡単に書き出せるよ - akiyoko blog2016/03/135.7
29見よ!これが Python製の WordPress風フルスタックCMSフレームワーク「Mezzanine(メザニン)」だ! - akiyoko blog2015/12/235.5
30「あなたの趣味は?」のアンケート結果を R で因子分析してみた - akiyoko blog2017/12/045.5

かなり前に書いた Python・Django 系の記事がいくつかランキングに返り咲いていたのが興味深いです。


今年の目標

今年は、引き続き Django を盛り上げていくのはもちろんのこと、今執筆している商業誌をしっかりと書き上げることを大目標にしていきたいです。今年もすでにいろいろとイベントが予定されていて忙しそうなのですが、なんとか乗り切っていきたいと思います。

今年もよろしくお願い致します。


f:id:akiyoko:20200103114643p:plain:w350

*1:純粋な PV数ではなく、作成日からの日数で割ったポイントで算出しています。

*2:作成日のところを黄色く塗っています。

Windows + Python 3.8 で pip install mysqlclient が失敗する原因と対策

$
0
0

Windows + Python 3.8 の環境下で pip install mysqlclient が失敗する原因と対策について、備忘録をまとめておきます。

結論

Windows 10 + Python 3.8 + pip 19.2.2 以前という環境で、mysqlclient 1.4.6 の pip インストールが失敗する。その際、「Microsoft Visual C++ 14.0 is required」あるいは「include ファイルを開けません。'mysql.h':No such file or directory」といったエラーが出る。

対策としては、pip のバージョンを 19.2.3 以降にアップグレードするだけ。

なお、Microsoft Visual C++ をインストールする必要はありません。



詳細

エラー(その1)

PC に Visual C++ がインストールされていない状況では、「error: Microsoft Visual C++ 14.0 is required」というエラーが出ます。しかし、Visual C++ をインストールしても後述するエラー(その2)が出てしまい、根本解決には至りません。

《 発生条件 》
  • Windows 10
  • Python 3.8.2
  • pip 19.2.2
  • Microsoft Visual C++ 14.0 以降が未インストール
《 エラー内容 》
(venv) C:\Users\akiyoko\work\mysqlclient-test>pip install --no-cache-dir mysqlclient
Collecting mysqlclient
  Downloading https://files.pythonhosted.org/packages/d0/97/7326248ac8d5049968bf4ec708a5d3d4806e412a42e74160d7f266a3e03a/mysqlclient-1.4.6.tar.gz (85kB)
     |████████████████████████████████| 92kB 990kB/s
Installing collected packages: mysqlclient
  Running setup.py install for mysqlclient ... error
    ERROR: Command errored out with exit status 1:
     command: 'c:\users\akiyoko\work\mysqlclient-test\venv\scripts\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\akiyoko\\AppData\\Local\\Temp\\pip-install-yluhih0a\\mysqlclient\\setup.py'"'"'; __file__='"'"'C:\\Users\\akiyoko\\AppData\\Local\\Temp\\pip-install-yluhih0a\\mysqlclient\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\akiyoko\AppData\Local\Temp\pip-record-4l7b4jv9\install-record.txt' --single-version-externally-managed --compile --install-headers 'c:\users\akiyoko\work\mysqlclient-test\venv\include\site\python3.8\mysqlclient'
         cwd: C:\Users\akiyoko\AppData\Local\Temp\pip-install-yluhih0a\mysqlclient\
    Complete output (24 lines):
    running install
    running build
    running build_py
    creating build
    creating build\lib.win-amd64-3.8
    creating build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\__init__.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\_exceptions.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\compat.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\connections.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\converters.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\cursors.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\release.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\times.py -> build\lib.win-amd64-3.8\MySQLdb
    creating build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\__init__.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\CLIENT.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\CR.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\ER.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\FIELD_TYPE.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\FLAG.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    running build_ext
    building 'MySQLdb._mysql' extension
    error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/
    ----------------------------------------
ERROR: Command errored out with exit status 1: 
    ...(略)...


ちなみに、Visual Studio Community 2019 のインストール時に、Visual C++ を合わせてインストールすることができます(根本解決にはならないので悪しからず)。手順を以下に示します(が、やるだけ無駄ですのでご注意を)。

Visual Studio 2019 のダウンロードページから、コミュニティ版のインストーラをダウンロードします。

f:id:akiyoko:20200420222420p:plain:w400

ダウンロードしたインストーラをダブルクリックすると、Visual Studio Installer が起動します。「個別のコンポーネント」から「Visual C++」を検索して、「C++ x64/x86 ビルドツール」を選択してインストールします。

f:id:akiyoko:20200420223318p:plain:w500

「Visual C++」がインストールできました。Windows の「アプリと機能」から確認可能です。

f:id:akiyoko:20200420223337p:plain:w400


なお、「Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019」は、https://support.microsoft.com/en-gb/help/2977003/the-latest-supported-visual-c-downloadsからも exe ファイルを直接ダウンロードしてインストールが可能です。PC の環境に合わせてご利用ください(私の環境は「x64」でした)。




エラー(その2)

PC にすでに Visual C++ がインストールされていても、pip のバージョンが 19.2.2 以前だと、「fatal error C1083: include ファイルを開けません。'mysql.h':No such file or directory」というエラーが出てしまいます。

《 発生条件 》
  • Windows 10
  • Python 3.8.2
  • pip 19.2.2
  • Microsoft Visual C++ 14.0 以降がインストール済み
《 エラー内容 》
(venv) C:\Users\akiyoko\work\mysqlclient-test>pip install --no-cache-dir mysqlclient
Collecting mysqlclient
  Downloading https://files.pythonhosted.org/packages/d0/97/7326248ac8d5049968bf4ec708a5d3d4806e412a42e74160d7f266a3e03a/mysqlclient-1.4.6.tar.gz (85kB)
     |████████████████████████████████| 92kB 845kB/s
Installing collected packages: mysqlclient
  Running setup.py install for mysqlclient ... error
    ERROR: Command errored out with exit status 1:
     command: 'c:\users\akiyoko\work\mysqlclient-test\venv\scripts\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\akiyoko\\AppData\\Local\\Temp\\pip-install-kp34ucod\\mysqlclient\\setup.py'"'"'; __file__='"'"'C:\\Users\\akiyoko\\AppData\\Local\\Temp\\pip-install-kp34ucod\\mysqlclient\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\akiyoko\AppData\Local\Temp\pip-record-3csqetin\install-record.txt' --single-version-externally-managed --compile --install-headers 'c:\users\akiyoko\work\mysqlclient-test\venv\include\site\python3.8\mysqlclient'
         cwd: C:\Users\akiyoko\AppData\Local\Temp\pip-install-kp34ucod\mysqlclient\
    Complete output (30 lines):
    running install
    running build
    running build_py
    creating build
    creating build\lib.win-amd64-3.8
    creating build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\__init__.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\_exceptions.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\compat.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\connections.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\converters.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\cursors.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\release.py -> build\lib.win-amd64-3.8\MySQLdb
    copying MySQLdb\times.py -> build\lib.win-amd64-3.8\MySQLdb
    creating build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\__init__.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\CLIENT.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\CR.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\ER.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\FIELD_TYPE.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    copying MySQLdb\constants\FLAG.py -> build\lib.win-amd64-3.8\MySQLdb\constants
    running build_ext
    building 'MySQLdb._mysql' extension
    creating build\temp.win-amd64-3.8
    creating build\temp.win-amd64-3.8\Release
    creating build\temp.win-amd64-3.8\Release\MySQLdb
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\bin\HostX86\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -Dversion_info=(1,4,6,'final',0) -D__version__=1.4.6 "-IC:\Program Files (x86)\MySQL\MySQL Connector C 6.1\include\mariadb" -Ic:\users\akiyoko\work\mysqlclient-test\venv\include -IC:\Users\akiyoko\AppData\Local\Programs\Python\Python38\include -IC:\Users\akiyoko\AppData\Local\Programs\Python\Python38\include "-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\include" /TcMySQLdb/_mysql.c /Fobuild\temp.win-amd64-3.8\Release\MySQLdb/_mysql.obj /Zl /D_CRT_SECURE_NO_WARNINGS
    _mysql.c
    MySQLdb/_mysql.c(29): fatal error C1083: include ファイルを開けません。'mysql.h':No such file or directory
    error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.25.28610\\bin\\HostX86\\x64\\cl.exe' failed with exit status 2
    ----------------------------------------
ERROR: Command errored out with exit status 1:
    ...(略)...

 

原因

根本原因

同じ環境でも pip のバージョンによって、対応している Python のバージョンと ABIタグの組み合わせが微妙に異なっているということが分かりました。具体的には、バージョン 19.2.3 の前後で

  • pip(バージョン 19.2.2 以前)では、Python バージョン「3.8」に対応する ABIタグは「cp38m」のみ
  • pip(バージョン 19.2.3 以降)では、Python バージョン「3.8」に対応する ABIタグは「cp38」のみ

となっていたのですが、この差は、

github.com

から出てきたものと思われます。
なお、対応している Python バージョンと ABIタグの組み合わせは、

>>> from pip._internal.pep425tags import get_supported
>>> get_supported()

の実行結果から確認可能です。


一方の mysqlclient は、PyPI でソースコード形式と(コンパイル済みの C拡張を含んだ)wheel形式が配布されているのですが、最新バージョンの「1.4.6」では、Pythonバージョン「3.8」と ABIタグ「cp38」の組み合わせに対応する 「mysqlclient‑1.4.6‑cp38‑cp38‑win_amd64.whl」は用意されているものの、Pythonバージョン「3.8」と ABIタグ「cp38m」の組み合わせに対応する「mysqlclient‑1.4.6‑cp38‑cp38m‑win_amd64.whl」は用意されておらず、ソースコードをローカルでビルドしようとしてエラーになっているものと推測されます。 *1

そもそも、wheel形式の配布物が利用できないのが根本原因で、これが利用できれば C++ によるビルドは必要ないはずです。


どうしてこのような状況になるのか?

Python をインストールしたときに合わせてインストールされた pip が古いままになっている(現在は最新の Python 3.8.2 をインストールするとバージョン「19.2.3」の pip が同梱されます)、あるいは、PyCharm 内部で利用されている pip のバージョンが古い(PyCharm 2019.3 では「19.0.3」だったが、最新版の PyCharm 2020.1 では「20.0.2」となっていてこの事象は解消済み)などの状況が考えられます。




*1:非公式ですが、Python Extension Packages for Windows - Christoph Gohlkeから確認可能

PyCharm のオレオレ最強設定(2020.1 バージョン)

$
0
0

PyCharm 大好き akiyoko です。

以前、「PyCharm のオレオレ最強設定」という記事を書いたのですが、あれから3年経ち、PyCharm もどんどん新しいバージョンがリリースされる中で記事の内容が少し古くなっていたので、今回、最新バージョンの「PyCharm 2020.1」に合わせて記事を書き直してみました。


f:id:akiyoko:20200508040938p:plain


PyCharm を含めた JetBrains社の IDE は「out of the box(箱から取り出してすぐに使える、難しい設定などは一切なしで使える)」というのが大きな魅力のひとつですが、今回紹介する環境設定をすることでさらなる実力を発揮させることができます。


なお、PyCharm には大きく分けて、無償版の「PyCharm CE(Community Edition)」と有償版の「PyCharm Professional」がありますが *1、「PyCharm CE」の設定は「PyCharm Professional」でもそのまま利用可能です。



今回の設定については

  • バージョン:PyCharm CE 2020.1
  • OS:Windows 10 Home / macOS 10.15 Catalina

で動作確認しています(画面キャプチャは Windows 版のもの)。


なお、今回紹介するのはあくまで筆者の(オレオレ)最強設定ですので、異論・反論は緩やかな範囲でお願いします。


 

PyCharm CE のインストール手順

まずは PyCharm CE 2020.1 のインストール手順について軽く説明します。


PyCharm 公式ダウンロードページから、「コミュニティ」(PyCharm CE)の方の「ダウンロード」ボタンをクリックして、インストーラをダウンロードします。

f:id:akiyoko:20200508034016p:plain


あとはインストーラをダブルクリックして、表示される内容に従ってインストールを実行してください。


f:id:akiyoko:20200501114537p:plain:w400

デフォルトでは、Windows の場合は

  • C:\Program Files\JetBrains\PyCharm Community Edition 2020.1

macOS の場合は

  • ~/Library/Application Support/JetBrains/PyCharmCE2020.1

にインストールされます。


 

環境設定

上部メニューの[File] >[Settings](macOS 版では[PyCharm] >[Preferences])から、PyCharm の環境設定が変更できます。

1.テーマ設定

[Appearance & Behavior] >[Appearance]

[Theme] をデフォルトの「IntelliJ Light」から、「Darcura」(少し暗めのテーマ)に変更します。あくまで任意で。 *2

f:id:akiyoko:20200501125359p:plain

理由:かっこいいから。


ちなみに、「Darcula」テーマはこんな感じの画面になります。

f:id:akiyoko:20200507035200p:plain


 

2.エディタ外観

[Editor] >[General] >[Appearance]

[Show whitespaces]にチェックを入れます。

f:id:akiyoko:20200501115211p:plain

「Leading(行頭)」「Inner(行内)」「Trailing(行末)」にもチェックが入っていることを確認してください。


理由:半角スペース、全角スペースを可視化したいから。


 

3.コード補完

[Editor] >[General] >[Code Completion]

[Match case]のチェックを外します。

f:id:akiyoko:20200501115517p:plain

理由:コード補完(Ctrl + スペース)の際に、大文字小文字を区別しないで補完してほしいから。

(参考)コード補完 - ヘルプ | PyCharm


 

4.コードの折りたたみ

[Editor] >[General] >[Code Folding]

[Fold by default]から、「Imports」のチェックを外します。

f:id:akiyoko:20200501173118p:plain

理由:import 文がデフォルトで折りたたまれていると、レビューしにくいから。


 

5.タブの表示数

[Editor] >[General] >[Editor Tabs]

[Closing Policy]の[Tab limit]を「10」から「50」に変更します。

f:id:akiyoko:20200501172330p:plain

理由:タブを 10 個以上開くと勝手に閉じちゃう設定だと使いにくいから。


 

6.フォントサイズ設定

[Editor] >[Font]

[Size]を「12」(デフォルトは「13」)くらいにしておきます。

f:id:akiyoko:20200501120518p:plain

理由:できるだけ広い範囲でコードを見たいから。


 

7.改行コード設定

[Editor] >[Code Style]

[General]タブの[Line separator]を「Unix and macOS (\n)」に変更します。macOS 版の場合は「System-Dependent」のままでOKです。

f:id:akiyoko:20200501120725p:plain

理由:macOS で編集したコードとの差異を無くしたいから。


 

8.Python コードスタイル設定

[Editor] >[Code Style]>[Python]

8.1. 一行あたりの文字数設定

[Wrapping and Braces]タブの[Hard wrap at]を「100」に変更します(デフォルトは「120」)。なお、一行あたりの文字数はプロジェクトのルールに適宜合わせてください。

f:id:akiyoko:20200501123713p:plain

理由:PyCharm の自動フォーマットを使ったときに規定文字数で自動的に折り返ししてほしいから。

8.2. import文のソート

[Imports]タブの[Sort import statements]で、「Sort imported names in "from" imports」(from 節でインポートした名前をソート)にチェックを入れます。

同じく、[Imports]タブの[Structure of "from" imports]は、デフォルトの「Leave as is」から「Join imports with the same source」(同じソースでインポートを結合)に変更します。

f:id:akiyoko:20200501124326p:plain

理由:

  • import文の自動フォーマットを使用したときに、from 節内の複数 import を自動ソートしてほしいから。
  • import文の自動フォーマットを使用したときに、from 節が同一の import 文を自動で一行にまとめてほしいから。


(参考)コード・スタイル: Python - ヘルプ | PyCharm


 

9.PEP8 違反の警告

[Editor] >[Inspections]

  • [PEP8 coding style violation]
  • [PEP8 naming conversion violation]

の「Severity」を「Week warning」から「Warning」に変更。

f:id:akiyoko:20200501184523p:plain

理由:PEP8 違反の警告をワーニングに上げたいから。


(参考)最強のPython統合開発環境PyCharm - Qiita


 

10.プラグイン

[Plugins]

から、次のプラグインをインストールします。

  • CodeGlance(Sublime Text のミニマップ風)

インストールしていないプラグインは、検索して「Install」ボタンをクリックするとオンラインのリポジトリからダウンロードしてインストールすることができます。


f:id:akiyoko:20200503160741p:plain


 

VMオプション設定

[Help] >[Edit Custom VM Options...]

を選択すると、VMオプションの設定をカスタマイズするためのファイルを編集することができます。 *3

11.メモリ設定

デフォルトのヒープメモリサイズを 2GB くらいに増やしておきます。たとえば、

-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
...(略)...

となっているのを、

-Xms128m
-Xmx2048m
-XX:ReservedCodeCacheSize=240m
...(略)...

と書き換えて、PyCharm を再起動します。


(参考)高度な構成 - ヘルプ | PyCharm


 

プロジェクトツールウィンドウ設定

プロジェクトビューの歯車アイコンをクリックして、ツールウィンドウの設定をおこないます。
 

12.Project ビューの表示設定

  • [Open Files with Single Click](プロジェクトビューからシングルクリックでソースビューを開く)
  • [Always Select Opened File](ソースビューを開くとプロジェクトビューに該当モジュールがフォーカスする)

にチェックを入れておきます。

f:id:akiyoko:20200501174811p:plain


 

日本語化

「Pleiades 日本語化プラグイン」を利用すれば、PyCharm の UI を日本語化することも可能です。ただし JetBrains 公式のものではないため、動作は保証されていないのでご注意ください。


pleiades.io

 

13.Pleiades 日本語化プラグインの導入手順

まず、「Pleiades 日本語化プラグイン」のダウンロードサイトから zip ファイルをダウンロードします(OS に合わせてダウンロードしてください)。

f:id:akiyoko:20200508043630p:plain

なお、PyCharm バージョン 2020.1 に対応した最新の Pleiades 日本語化プラグインを使用しないと動作しないのでご注意ください。 *4



zipファイルを解凍し、setup.exe(macOS の場合は setup.app)をダブルクリックしてインストーラを起動します。


f:id:akiyoko:20200508043953p:plain:w400

「日本語化するアプリケーション」の「選択」ボタンをクリックして、Windows の場合は

  • C:\Program Files\JetBrains\PyCharm Community Edition 2020.1\bin\pycharm64.exe

macOS の場合は

  • /Applications/PyCharm CE.app

を選択して、「日本語化する」ボタンをクリックします。


f:id:akiyoko:20200508043705p:plain:w400

あとは、PyCharm を再起動すれば OK です。


f:id:akiyoko:20200508043718p:plain


 

便利なショートカット集

最後に非常に便利なショートカットをいくつか紹介します。

JetBrains 公式の PyCharm チートシート

もありますが、さすがに全部覚えきれませんよね。そこで、私が特に重要と考えるショートカットを抜粋してみました。黄色で強調したショートカットだけでも覚えておくと、効率が劇的に上がりますのでぜひ活用してください。

 

コードフォーマット
Windows
macOS
説明
Ctrl + Alt + Loption + ⌘ + Lコードの自動フォーマット
Ctrl + Alt + Ocontrol + option + Oimport文の自動フォーマット

 

検索
Windows
macOS
説明
Ctrl + F⌘ + Fファイル内検索
(Shift +) F3⌘ + (shift +) Gファイル内検索結果の前後を表示
Ctrl + Shift + F⌘ + shift + Fパス内検索
Alt + F7option + F7関数・メソッドを使用している箇所を検索して Find ウィンドウに表示
Ctrl + Alt + ↓ (↑)⌘ + option + ↓ (↑)Findウィンドウの検索結果の前後を表示
Ctrl + Alt + ← (→)⌘ + option + ← (→)履歴の前後を表示
Ctrl + B⌘ + B関数・メソッドの宣言箇所にジャンプ

 

ファイルを開く
Windows
macOS
説明
Shift x 2 (素早く2回)shift x 2 (素早く2回)クイック検索
Ctrl + E⌘ + E最近開いたファイルを開く
Ctrl + Shift + N⌘ + shift + Oファイル名でファイルを開く

 

タブ移動
Windows
macOS
説明
Alt + ← (→)control + ← (→)タブ移動

 

差分表示
Windows
macOS
説明
(Project ビューで) Ctrl + D(Project ビューで) ⌘ + D別ファイルとの Diff

 

その他
Windows
macOS
説明
Ctrl + Shift + S⌘ + ,(カンマ)環境設定を開く
Ctrl + Shift + A⌘ + shift + A利用できるアクションを検索

 

まとめ

個人的に、PyCharm は Python の統合開発環境(IDE)として最強だと考えています。デフォルトの環境設定を何もイジらなくても快適に動いてくれるというのも大きな魅力のひとつが、今回のような環境設定をすると PyCharm はさらに使いやすくなります。


それでは、素敵な PyCharm ライフを!

参考

*1:その他にも Educational 版があります

*2:macOS 版ではデフォルトが「Darcura」になっているようです。少なくとも私の環境では。

*3:Windows の場合は「~\AppData\Roaming\JetBrains\PyCharmCE2020.1\pycharm64.exe.vmoptions」、macOS の場合は「~/Library/Application Support/JetBrains/PyCharmCE2020.1/pycharm.vmoptions」がデフォルトです。

*4:IntelliJ IDEA / PyCharm 等 JetBrains IDE 2020.1 アップデート関連の問題と対処 | JetBrains ブログ

Django モデルのフィールドの「auto_now_add」「auto_now」オプションの挙動を詳しく調べてみた

$
0
0

Django モデルに登録日時や更新日時のフィールドを付加する場合、「auto_now_add」オプションや「auto_now」オプションを利用すると便利です。それらのオプションの挙動について詳しく調べてみたので、まとめておきます。

f:id:akiyoko:20200521233958p:plain

TL;DR


挙動

たとえば、

shop/models.py

from django.db import models


classBook(models.Model):
    """本モデル"""classMeta:
        db_table = 'book'
        verbose_name = verbose_name_plural = '本'

    title = models.CharField('タイトル', max_length=255)
    price = models.PositiveIntegerField('価格', null=True, blank=True)
    created_at = models.DateTimeField('登録日時', auto_now_add=True)
    updated_at = models.DateTimeField('更新日時', auto_now=True)

    def__str__(self):
        return self.title

というフィールドがあるとすると、

登録時

  • 「auto_now_add=True」のフィールド(created_at)に現在日時が自動的にセットされる
  • 「auto_now=True」のフィールド(updated_at)に現在日時が自動的にセットされる

更新時

  • 「auto_now=True」のフィールド(updated_at)に現在日時が自動的にセットされる

という挙動になります。


つまり、テストを書くとこういうことになります(参考までに freezegununittest.mockを使ったものを併記)。


shop/tests/test_models.py

from datetime import datetime
from unittest.mock import patch

from django.test import TestCase
from django.utils import timezone
from freezegun import freeze_time

from ..models import Book


classTestBook(TestCase):

    @freeze_time('2020-01-01 01:01:01')
    deftest_save_with_freezegun(self):
        book = Book(title='Django本', price=1500)
        # 登録
        book.save()

        # 登録日時・更新日時のどちらも値がセットされる
        self.assertEqual(book.created_at, datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc))
        self.assertEqual(book.updated_at, datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc))

        with(freeze_time('2020-02-02 02:02:02')):
            # 更新
            book.save()

        # 更新日時のみが更新される
        self.assertEqual(book.created_at, datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc))
        self.assertEqual(book.updated_at, datetime(2020, 2, 2, 2, 2, 2, tzinfo=timezone.utc))

    @patch('django.utils.timezone.now',
           return_value=datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc))
    deftest_save_with_mock(self, _mock_now):
        book = Book(title='Django本', price=1500)
        # 登録
        book.save()

        # 登録日時・更新日時のどちらも値がセットされる
        self.assertEqual(book.created_at, datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc))
        self.assertEqual(book.updated_at, datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc))

        _mock_now.return_value = datetime(2020, 2, 2, 2, 2, 2, tzinfo=timezone.utc)
        # 更新
        book.save()

        # 更新日時のみが更新される
        self.assertEqual(book.created_at, datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc))
        self.assertEqual(book.updated_at, datetime(2020, 2, 2, 2, 2, 2, tzinfo=timezone.utc))



 
フィールドに現在日時が自動的にセットされる仕組みを見てみると、次のように、pre_save() で django.utils.timezone.now() が呼ばれています。

django.db.models.fields.DateTimeField.pre_save *1

defpre_save(self, model_instance, add):
        if self.auto_now or (self.auto_now_add and add):
            value = timezone.now()
            setattr(model_instance, self.attname, value)
            return value
        else:
            returnsuper().pre_save(model_instance, add)


そして django.utils.timezone.now() では、Django の設定ファイルの「USE_TZ」が True の場合は datetime.datetime.utcnow() が呼ばれています。

django.utils.timezone.now *2

defnow():
    """    Return an aware or naive datetime.datetime, depending on settings.USE_TZ."""if settings.USE_TZ:
        # timeit shows that datetime.now(tz=utc) is 24% slowerreturn datetime.utcnow().replace(tzinfo=utc)
    else:
        return datetime.now()


freezegun の freezegun.api.FakeDatetime が次のように datetime.datetime.now() や utcnow() などをモックしてくれているので、「auto_now_add」や「auto_now」にも適用できるのですね。よかった。

freezegun.api.FakeDatetime *3

classFakeDatetime(with_metaclass(FakeDatetimeMeta, real_datetime, FakeDate)):

    ...(略)...

    @classmethoddefnow(cls, tz=None):
        now = cls._time_to_freeze() or real_datetime.now()
        if tz:
            result = tz.fromutc(now.replace(tzinfo=tz)) + cls._tz_offset()
        else:
            result = now + cls._tz_offset()
        return datetime_to_fakedatetime(result)

    ...(略)...

    @classmethoddefutcnow(cls):
        result = cls._time_to_freeze() or real_datetime.utcnow()
        return datetime_to_fakedatetime(result)

    ...(略)...


 

「auto_now_add」や「auto_now」を使うことによる弊害

便利でよいのですが、二点ほど弊害があります。

1.任意の値で更新できない

実例を挙げると、

>>> from shop.models import Book
>>> from datetime import datetime
>>> from django.utils import timezone
>>> book = Book()
>>> book.updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc)
>>> book.save()
>>> book.updated_at
datetime.datetime(2020, 5, 21, 6, 41, 7, 219328, tzinfo=<UTC>)

という感じで、保存時に現在日時で上書きされてしまいます。

(参考)www.ianlewis.org


しかしながら、登録日時や更新日時フィールドに「auto_now_add」および「auto_now」オプションを利用する場合、任意の値で更新したいユースケースとしてはユニットテストのときくらいでしょうから、実害はあまり無いと言えるかもしれません。
 

2.管理サイトの詳細画面で表示されない

「auto_now_add」もしくは「auto_now」オプションを利用すると、自動的に editable オプションが False になるので、管理サイトの詳細画面で表示されなくなります。

f:id:akiyoko:20200521232712p:plain

django.db.models.fields.DateField.__init__ *4

classDateField(DateTimeCheckMixin, Field):
    ...(略)...

    def__init__(self, verbose_name=None, name=None, auto_now=False,
                 auto_now_add=False, **kwargs):
        self.auto_now, self.auto_now_add = auto_now, auto_now_add
        if auto_now or auto_now_add:
            kwargs['editable'] = False
            kwargs['blank'] = Truesuper().__init__(verbose_name, name, **kwargs)

    ...(略)...

classDateTimeField(DateField):
    ...(略)...

    # __init__ is inherited from DateField


詳細画面にフィールドを表示するだけであれば(任意の値で登録・更新できないが)、ModelAdmin の「fields」と「readonly_fields」に同時に列挙することで対応は可能です。

shop/admin.py

from django.contrib import admin

from .models import Book


classBookAdmin(admin.ModelAdmin):
    fields = ('title', 'price', 'created_at', 'updated_at')
    readonly_fields = ('created_at', 'updated_at')


admin.site.register(Book, BookAdmin)

f:id:akiyoko:20200521233140p:plain



これらの弊害については一応解決策がありますが、ちょっと面倒です。あまりオススメしません。

stackoverflow.com



 

宣伝

Django の本を3冊書きました。Django 開発のお供にどうぞ。

現場で使える Django の教科書《基礎編》

★ Amazon(電子版/ペーパー版)


★ BOOTH(ペーパー版)


現場で使える Django の教科書《実践編》

★ Amazon(電子版)


★ BOOTH(ペーパー版)


現場で使える Django REST Framework の教科書

★ Amazon(電子版)


★ BOOTH(ペーパー版)


新刊『現場で使える Django 管理サイトのつくり方』頒布のお知らせ

$
0
0

2020/9/12(土)から開催される「技術書典9@技術書典オンラインマーケット」まであと1ヶ月となりましたが、そこで「あきよこブログ」として5回目のサークル参加をします。


4冊目の新刊は『現場で使える Django 管理サイトの作り方』です。


f:id:akiyoko:20200807103518p:plain:w350

安心してください。今回も Django 本ですよ~ 😉


タイトルからお察しの通り、Django の管理サイト(Django Admin)だけにフォーカスした、ニッチでオンリーワンな一冊です。注目すべきはイカレたその分厚さ。「Django」という Python 製の Webフレームワークの中の「管理サイト」という一機能だけに特化したオンリー本でありながら、本文 140ページの大ボリュームに仕上がっています。


f:id:akiyoko:20200808134231p:plain:w500

技術書典9の開催まであと1ヶ月あるのですが、実は すでに執筆は終わっていて、あとは入稿するだけという状況です。というのも、この本は2月・3月に開催予定だった「技術書典8」で頒布するはずだったのですが、新型コロナウィルスの感染拡大防止に伴ってイベント自体がなくなってしまった(その後オンラインで開催)のと子供の出産が4月に控えていたため、8割ほど完成させていたにもかかわらず途中で放置してしまっていたのでした。そしてこの度、育休を取れたのをきっかけに無事執筆を終えることができたという次第なのです。

そしてこの度、ヒマだったので事前にアンケート調査をしてみました(アンケートはすでに締め切っています)。

docs.google.com

皆さま、ご協力ありがとうございました。アンケートの結果発表を兼ねて、新刊『現場で使える Django 管理サイトのつくり方』がどんな本なのか? どんな課題を解決してくれるのか? について解説します。


どんな本なの?

皆さんは、管理サイト(Django Admin)を使っていますか?

おそらく Django を利用している開発者の ほとんどが「イエス」と答えるでしょう。実際、事前アンケートでは Django 利用者の9割近くが「いつも使っている」あるいは「たまに使っている」と回答しています。


f:id:akiyoko:20200812091900p:plain:w450

しかしながら、その仕組みをきちんと理解せずに使っている人が意外と多いのではないでしょうか。管理サイトは Django の仕組みや設計思想をうまく活かしたアプリになっており、管理サイトを理解することはそれらを知る手助けにもなります。これからも Django を長く使っていくのであれば、管理サイトを深く知っておくことは必ず強力な武器になるでしょう。


また、管理サイトはあらゆるモデルに対応できるように汎用的に作られており、ある程度のカスタマイズも考慮されていて幅広いユースケースで利用することができます。実際の現場では「開発中のテストデータ投入」や「システム利用ユーザーの情報管理」で利用しているケースが多いようです。


f:id:akiyoko:20200812091920p:plain:w500

管理サイトには利用するメリットがたくさんありますが、これについても事前にアンケートを採ってみました。


f:id:akiyoko:20200812091936p:plain:w600

結果を見ると、デフォルトで使える点やほんの数行書くだけでモデルの CRUD 機能が追加できる点が特に高く評価されていて、お手軽で簡単に使えるというのが管理サイトの大きなメリットになっています。

しかし、当然ながら管理サイトは「万能」ではありません。たとえば「少しカスタマイズすれば一般ユーザー向けの画面として使えそう」と目論んでいたら、後になって管理サイトの特性や限界を無視した要望がいっぱい出てきて余計に工数が膨らんでしまったというのはありがちな失敗パターンです。

メリットばかりがクローズアップされがちな管理サイトですが、ここで敢えて負の面に目を向けてみます。次のアンケート結果を見てください。


f:id:akiyoko:20200812092003p:plain:w600

「ある程度以上のカスタマイズになると難易度が上がる」や「簡単にカスタマイズできるかどうかのジャッジにノウハウや調査が必要」など、カスタマイズ系のデメリットが圧倒的に多いことが分かります。「日本語の情報が少ない」や「仕様を把握するのにひと苦労」が多いのは、困ったときのヒントが得られにくいという状況を表しているものと考えられます。これらを踏まえると、管理サイトをカスタマイズする場合は 事前にその限界(基本仕様でどこまでできるのか)とカスタマイズの特性(どんなカスタマイズが簡単でどんなカスタマイズが難しいのか)を十分に把握しておく必要があるでしょう。


そこで本書では、いつも使う管理サイトだからこそ知っておきたい現場レベルの知識やノウハウについて、次の3つのポイントを中心に解説していきます。

  1. 管理サイトの基本仕様
  2. 管理サイトの仕組みを活かしたカスタマイズ戦略
  3. カスタマイズ後のテスト


本書を読めば、管理サイトの基本から応用に至るまでの幅広い知識が得られ、Django への理解がさらに深まるでしょう。


 

対象読者

本書の読者としては、

  • 管理サイト(Django Admin)のことをもっと知りたい方
  • これから管理サイトのカスタマイズをしようとしている方

を想定しています。

特に、これから管理サイトのカスタマイズをしようとしている方には是非とも読んでほしい内容になっています。

もし管理サイトに興味がなくても、

  • ユーザーモデルやパーミッションの仕組み
  • テンプレートや静的ファイルのルックアップの仕組み
  • Selenium を使ったブラウザテストの書き方

などに興味があれば刺さるかもしれません。

最低限必要な知識としては「Django の仕組みが何となく理解できていること」です。Django 公式チュートリアルDjango Girls チュートリアルをひと通り終えたくらいであれば問題はないでしょう。また、拙著『現場で使える Django の教科書《基礎編》』を読み終えたくらいの知識があれば万全です。



以降で、章ごとの読みどころを紹介していきます。



第1章: 管理サイトの基本仕様

管理サイトは意外と機能が豊富で、その仕様を把握するのにもひと苦労です。そこで本書では手始めに、管理サイトの基本仕様を詳しく紹介しています。

まず、管理サイトの全体像が捉えやすいように画面遷移図(紙面サンプル ① を参照)を示しています。こういった画面遷移図ってググっても何故かなかなか見つからないんですよね。。

その後、Django 初心者向けに利用手順を紹介したあとで、管理サイトが利用している Django の仕組みである「ユーザーモデル」「パーミッションによるアクセス制御」「変更履歴」について解説しています。パーミッションあたりはあまり理解していない人も多いのではないでしょうか。

最後に、画面ごとに詳細な説明をしています。管理サイトは普段は気付かないような隠れ機能があったりするので、手の込んだ仕掛けに驚かされるでしょう。


  • 第1章: 管理サイトの基本仕様
    • 1.1: 管理サイトとは
    • 1.2: 基本機能
    • 1.3: 利用手順
    • 1.4: ユーザーモデルについて
    • 1.5: パーミッションによるアクセス制御
    • 1.6: 変更履歴について
    • 1.7: 各画面の詳細説明
    • 1.8: まとめ


《 紙面サンプル ① 》

f:id:akiyoko:20200806155855p:plain

《 紙面サンプル ② 》

f:id:akiyoko:20200807014635p:plain

第2章: 管理サイトのカスタマイズ

この章で気を付けたのは、なるべく図を多くするということです。公式ドキュメントや特に Stack Overflow などで検索した場合は図が無かったりするので、「一体どんなカスタマイズができるのかイメージが掴めない」ということが多いのです。そういう不満を払しょくするために、このカスタマイズをするとどんなことが実現できるのかがビジュアルで掴めるようにしてみました。

そしてこの章の目玉は、カスタマイズに利用できる AdminSite と ModelAdmin のクラス変数とメソッドの一覧表(紙面サンプル ④ を参照)と、管理サイトで使われるテンプレートファイルの一覧表です。こういうのがあると便利だなという気持ちと書くのがめちゃくちゃ大変だなという気持ちで随分葛藤しましたが、今となっては書いて正解だったと感じています。


  • 第2章: 管理サイトのカスタマイズ
    • 2.1: 内部構造とカスタマイズ方針
    • 2.2: AdminSite を利用したカスタマイズ
    • 2.3: ModelAdmin を利用したカスタマイズ
    • 2.4: テンプレートのカスタマイズ
    • 2.5: CSS のカスタマイズ
    • 2.6: Django パッケージを使ったカスタマイズ
    • 2.7: まとめ


《 紙面サンプル ③ 》

f:id:akiyoko:20200807014228p:plain

《 紙面サンプル ④ 》

f:id:akiyoko:20200807113843p:plain


第3章: 管理サイトのテスト

管理サイトのカスタマイズをする場合は AdminSite や ModelAdmin のクラス変数、メソッドをオーバーライドすることになりますが、それらの断片化したコードだけをユニットテストして例えカバレッジを100%にしたところで、機能が想定通りに動作することを保証したことにはなりません。そんなわけで、管理サイトのテストでは画面ごとにビューのテストをした上で、「lxml」などのパッケージを利用してレンダリングされた HTML から要素をパースして検証するなどの工夫が必要になります。第3章の前半では、そういったテストケースのコード例を挙げて解説しています。

章の後半では、Selenium を使ったブラウザテストについて解説しています。Selenium を使ったテストでは通常、テンプレートの HTML 要素自体や class 属性に変更が加わるとテスト側にも修正を加えねばならず保守が大変になりますが、管理サイトはテンプレートが固まっているためブラウザテストとの相性がよいです。特に、管理サイトでは、Selenium によるブラウザテストをするための AdminSeleniumTestCase が提供されています。章の最後に、このクラスを継承したテストコード例を紹介しています。


  • 第3章: 管理サイトのテスト
    • 3.1: テスト方針
    • 3.2: 通常のユニットテスト
    • 3.3: Selenium によるブラウザテスト
    • 3.4: まとめ


《 紙面サンプル ⑤ 》

f:id:akiyoko:20200806160103p:plain

《 紙面サンプル ⑥ 》

f:id:akiyoko:20200807013459p:plain



頒布本情報

これから入稿するので価格はまだ決めていませんが、技術書典9向けの特別価格として「送料込み 1,200円」くらいを予定しています。

価格については決まり次第、更新します。



最後に

今回、いわゆる「管理サイト本」を書いた理由としては、自分の知識の整理のためということもありますが、どちらかというと Django をもっと現場に普及させたいという気持ちの方が強いです。つまり、現場で頻繁に使う管理サイトのまとまった日本語情報があれば、Django がもっと現場で使われやすくなるんじゃないかと思ったのです。


きっかけは、現場で管理サイトのカスタマイズ案件が二つ続いたことでした。そこには主にレビューで参加したのですが、「担当者が違うとこんなにも書き方が違うのか。保守が大変そうだなぁ」「わざわざこんなことしなくてもフックポイントがあるのに」「あらら、全部 Selenium テストで書いちゃったのね」などとガク然としたのです。そのときに「これを見といてね」と言えるものがなかった苦い経験から、管理サイトを少し真面目に使おうとするときに現場に一冊あれば安心な本を書こうと思い至ったのでした。


ということで、Django 開発のお供に『現場で使える Django 管理サイトのつくり方』を是非どうぞ!!😊


f:id:akiyoko:20200812092310p:plain:w200


 

宣伝

これまで Django の本を3冊出しました。Django 開発のお供にどうぞ。

現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文180ページ。


★ Amazon(電子版/ペーパー版)


★ BOOTH(ペーパー版)


現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)


現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文204ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)

Django のトランザクションについて(初級者向け)

$
0
0

この投稿は 「Django Advent Calendar 2020 - Qiita」 3日目の記事です。

akiyoko です。
Django のトランザクションについては拙著『現場で使える Django の教科書《基礎編》』の第6章「モデル(Model)」でも触れていますが、この記事ではもう少し詳しく解説してみたいと思います。




検証環境
  • Windows 10 Home
  • Django 3.1
  • PostgreSQL 13.1



 

はじめに

トランザクションは連続した複数のデータベース操作をひとつにまとめたものです。

トランザクションは、データベースをある一貫した状態から別の一貫した状態へ変更するアクションを1つに束ねたものである。トランザクション処理は、既知の一貫した状態のデータベースを維持するよう設計されており、相互依存のある複数の操作が全て完了するか、全てキャンセルされることを保証する。


トランザクション処理 - Wikipedia


一連の処理がすべて完了したらレコードの登録・変更・削除をまとめてデータベースに反映(コミット)し、エラーなどで一連の処理が完了しなかった場合はその過程でおこなわれたレコードの登録・変更・削除を無かったことにする(ロールバック)必要がある場合にトランザクションが利用されます。


 

デフォルトはオートコミット

Django のデフォルトでは、データベースのレコードを登録・更新・削除するための各クエリは、モデルオブジェクトの save() や delete() が実行された時点で即座にデータベースに反映されます。これは「オートコミットモード」と呼ばれます。*1

つまり、デフォルトではトランザクションは利用されません。


例えば、本の購入決済をするためのビューの中で、次のように連続したレコード操作があるとします。

f:id:akiyoko:20201127151205p:plain

デフォルトでは、「① 注文情報を登録」「③ 在庫数を 1 減らす」「④ 注文情報のステータスを更新」といったクエリはそれぞれの時点でコミット(データベースに反映)されます。

PostgreSQL の SQL ログは次のように出力されます。


《 PostgreSQL の SQL ログ 》

[pid-1] SET TIME ZONE 'UTC'
[pid-1] SELECT "book"."id", "book"."title", "book"."price" FROM "book" WHERE "book"."id" = 1 LIMIT 21
[pid-1] INSERT INTO "order" ("status", "total_amount", "ordered_by_id", "ordered_at") VALUES ('01', 1000, 1, '2020-12-01T03:00:18.900883+00:00'::timestamptz) RETURNING "order"."id"
[pid-1] SELECT "stock"."id", "stock"."book_id", "stock"."quantity", "stock"."updated_at" FROM "stock" WHERE "stock"."book_id" = 1 LIMIT 21
[pid-1] UPDATE "stock" SET "book_id" = 1, "quantity" = 0, "updated_at" = '2020-12-01T03:00:18.927935+00:00'::timestamptz WHERE "stock"."id" = 1
[pid-1] UPDATE "order" SET "status" = '02', "total_amount" = 1000, "ordered_by_id" = 1, "ordered_at" = '2020-12-01T03:00:18.900883+00:00'::timestamptz WHERE "order"."id" = 1

この中の INSERT や UPDATE は逐一コミットされます。




モデル、およびビューのソースコードのイメージは次の通りです。

shop/models.py(モデル)

from django.contrib.auth import get_user_model
from django.db import models

User = get_user_model()


classBook(models.Model):
    """本モデル"""classMeta:
        db_table = 'book'
        verbose_name = verbose_name_plural = '本'

    title = models.CharField('タイトル', max_length=255, unique=True)
    price = models.PositiveIntegerField('価格', null=True, blank=True)

    def__str__(self):
        return self.title


classBookStock(models.Model):
    """在庫モデル"""classMeta:
        db_table = 'stock'
        verbose_name = verbose_name_plural = '在庫'

    book = models.OneToOneField(Book, verbose_name='本', on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField('在庫数', default=0)
    updated_at = models.DateTimeField('更新日時', auto_now=True)

    def__str__(self):
        return f'{self.book.title} ({self.quantity})'classOrder(models.Model):
    """注文モデル"""classMeta:
        db_table = 'order'
        verbose_name = verbose_name_plural = '注文'

    STATUS_PAYMENT_PROCESSING = '01'
    STATUS_PAYMENT_OK = '02'
    STATUS_PAYMENT_NG = '03'
    STATUS_PAYMENT_ERROR = '09'
    STATUS_CHOICES = (
        (STATUS_PAYMENT_PROCESSING, '決済中'),
        (STATUS_PAYMENT_OK, '決済OK'),
        (STATUS_PAYMENT_NG, '決済NG'),
        (STATUS_PAYMENT_ERROR, '決済エラー'),
    )

    status = models.CharField('ステータス', max_length=2, choices=STATUS_CHOICES)
    total_amount = models.PositiveIntegerField('金額合計')
    ordered_by = models.ForeignKey(User, verbose_name='注文者', on_delete=models.PROTECT, editable=False)
    ordered_at = models.DateTimeField('注文日時', auto_now_add=True)

    def__str__(self):
        return f'{self.get_status_display()} ({self.ordered_at:%Y-%m-%d %H:%M})'


shop/views.py(ビュー)

from django.http.response import HttpResponseRedirect
from django.shortcuts import get_object_or_404, reverse
from django.views import View

from .models import Book, BookStock, Order


classCheckoutView(View):
    ...(略)...

    defpost(self, request, *args, **kwargs):
        book = get_object_or_404(Book, pk=kwargs['pk'])

        # ① 注文情報を登録
        order = Order(
            status=Order.STATUS_PAYMENT_PROCESSING,
            total_amount=book.price,
            ordered_by=request.user,
        )
        order.save()

        # ② 在庫数を確認
        book_stock = get_object_or_404(BookStock, book=book)
        # ③ 在庫数を1減らして更新
        book_stock.quantity -= 1
        book_stock.save()

        ...(決済処理)...

        # ④ 注文情報のステータスを更新
        order.status = Order.STATUS_PAYMENT_OK
        order.save()

        ...(略)...

        return HttpResponseRedirect(reverse('shop:checkout_complete'))


PostgreSQL へのデータベース接続設定は次のようになります。

config/settings.py(設定ファイル)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mysite',
        'USER': 'mysiteuser',
        'PASSWORD': 'mysiteuserpass',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}


ここまでは OK ですよね。



次に、ビューの中で予期せぬエラーが発生した場合について考えてみましょう。

f:id:akiyoko:20201127151541p:plain


デフォルトのオートコミットモードでは、エラーが発生する前の処理はすでにコミットされてしまっているので、プログラムで明示的に後始末処理(いわゆる手動ロールバック)をしないといけません。いろんなところで予期せぬエラーが発生することを考えると、いちいち手動でロールバック処理を書くのは大変ですよね。そこで Django ORM が提供している「トランザクション」 *2の機能を利用します。


f:id:akiyoko:20201127152322p:plain

このように一連の処理をトランザクションで囲んでおけば、エラーが発生した場合のロールバック処理を自動でおこなってくれるのです。


Django でトランザクションを利用するには、大きく次の二つの方法があります。

  • 【方法1】ATOMIC_REQUESTS を有効化する
  • 【方法2】transaction.atomic() で囲む


それぞれについて詳しく見ていきましょう。



 

【方法1】ATOMIC_REQUESTS を有効化する

トランザクションを適用するには、データベースの「ATOMIC_REQUESTS」設定を有効化するのが一番手っ取り早いです。有効化するには、次のように設定ファイルの「DATABASES」の「ATOMIC_REQUESTS」を True(デフォルトは False)にします。*3


config/settings.py(設定ファイル)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mysite',
        'USER': 'mysiteuser',
        'PASSWORD': 'mysiteuserpass',
        'HOST': 'localhost',
        'PORT': '5432',
        'ATOMIC_REQUESTS': True,  # 追加
    }
}


モデルやビューには何も手を加える必要はないので簡単ですよね。これだけで、ビュー全体のデータベース操作がひとつのトランザクションで囲まれます。


f:id:akiyoko:20201127170848p:plain


なお、ミドルウェア内のデータベース操作はこのトランザクションの範囲外となるので要注意です(あくまでビューの開始から終了までが一つのトランザクションになります)。



SQLクエリログは次のように出力されます。

《 PostgreSQL の SQL ログ 》

[pid-1] SET TIME ZONE 'UTC'
[pid-1] BEGIN
[pid-1] SELECT "book"."id", "book"."title", "book"."price" FROM "book" WHERE "book"."id" = 1 LIMIT 21
[pid-1] INSERT INTO "order" ("status", "total_amount", "ordered_by_id", "ordered_at") VALUES ('01', 1000, 1, '2020-12-01T03:01:53.319483+00:00'::timestamptz) RETURNING "order"."id"
[pid-1] SELECT "stock"."id", "stock"."book_id", "stock"."quantity", "stock"."updated_at" FROM "stock" WHERE "stock"."book_id" = 1 LIMIT 21
[pid-1] UPDATE "stock" SET "book_id" = 1, "quantity" = 0, "updated_at" = '2020-12-01T03:01:53.334689+00:00'::timestamptz WHERE "stock"."id" = 1
[pid-1] UPDATE "order" SET "status" = '02', "total_amount" = 1000, "ordered_by_id" = 1, "ordered_at" = '2020-12-01T03:01:53.319483+00:00'::timestamptz WHERE "order"."id" = 2
[pid-1] COMMIT

すべての SQL が「BEGIN」と「COMMIT」で囲まれていて、これがひとつのトランザクションになっています。



予期せぬエラーなどで ビューから例外が漏れた場合、Django ORM によるロールバックが自動でおこなわれます。その際の SQL ログは次のようになります。ちなみに、検査制約「CONSTRAINT stock_quantity_check CHECK (quantity >= 0)」が付いた在庫テーブルの在庫数(quantity)が「0」の場合に在庫を減らそうとしてエラーが出ちゃった例です。

《 PostgreSQL の SQL ログ 》

[pid-1] SET TIME ZONE 'UTC'
[pid-1] BEGIN
[pid-1] SELECT "book"."id", "book"."title", "book"."price" FROM "book" WHERE "book"."id" = 1 LIMIT 21
[pid-1] INSERT INTO "order" ("status", "total_amount", "ordered_by_id", "ordered_at") VALUES ('01', 1000, 1, '2020-12-01T03:02:29.849050+00:00'::timestamptz) RETURNING "order"."id"
[pid-1] SELECT "stock"."id", "stock"."book_id", "stock"."quantity", "stock"."updated_at" FROM "stock" WHERE "stock"."book_id" = 1 LIMIT 21
[pid-1] UPDATE "stock" SET "book_id" = 1, "quantity" =  -1, "updated_at" = '2020-12-01T03:02:29.856001+00:00'::timestamptz WHERE "stock"."id" = 1
[pid-1] ERROR:  new row for relation "stock" violates check constraint "stock_quantity_check"
[pid-1] ROLLBACK

ERROR の後に、ROLLBACK がおこなわれています。



「ATOMIC_REQUESTS」の利点は導入の手軽さですが、お手軽な反面、公式ドキュメントにもあるように、同時アクセス数が多いようなシステムではスループットを下げてしまう可能性もあり、性能面での注意が必要です。

このトランザクションモデルは簡潔ではありますが、トラフィックが増加するときには非効率となります。全てのビューでトランザクションを扱うとオーバーヘッドが増加します。パフォーマンスへの影響は、アプリケーションのクエリパターンと、どれだけうまくデータベースがロッキングを扱うかに依存します。


データベースのトランザクション | Django ドキュメント | Django


 

【方法2】transaction.atomic() で囲む

「ATOMIC_REQUESTS」の代わりに、transaction.atomic()を使ってトランザクションを実現する方法もあります。「ATOMIC_REQUESTS」がビュー全体をトランザクションで囲むのに対し、transaction.atomic() は with 句を使ってトランザクションの適用範囲を自由に設定することができます。


先の購入決済のビューの例では、在庫数の更新は可能な限り迅速にデータベースに反映しておく必要があるでしょう。例えば、在庫があと「1」しか残っていない状態で、あるユーザが決済処理を実行中だと、在庫は実質「0」ですよね。このタイミングで他のユーザが在庫テーブルを参照した場合に在庫数が「1」のままにならないように、在庫数を減らす更新を決済処理の前に一旦コミットして在庫を引き当て(取り置き)しておくようにしてみます。


transaction.atomic() を適用したコード例は次のようになります。設定ファイルの「ATOMIC_REQUESTS」の設定は無効化しておきます。

shop/views.py(ビュー)

from django.db import transaction  # 追加from django.http.response import HttpResponseRedirect
from django.shortcuts import get_object_or_404, reverse
from django.views import View

from .models import Book, BookStock, Order


classCheckoutView(View):
    ...(略)...

    defpost(self, request, *args, **kwargs):
        book = get_object_or_404(Book, pk=kwargs['pk'])

        with transaction.atomic():  # 追加# ① 注文情報を登録
            order = Order(
                status=Order.STATUS_PAYMENT_PROCESSING,
                total_amount=book.price,
                ordered_by=request.user,
            )
            order.save()

            # ② 在庫数を確認
            book_stock = get_object_or_404(BookStock, book=book)
            # ③ 在庫数を1減らして更新
            book_stock.quantity -= 1
            book_stock.save()

        ...(決済処理)...

        with transaction.atomic():  # 追加# ④ 注文情報のステータスを更新
            order.status = Order.STATUS_PAYMENT_OK
            order.save()

        ...(略)...

        return HttpResponseRedirect(reverse('shop:checkout_complete'))


これで、次のようにトランザクションを細かく分けることができるようになりました。*4

f:id:akiyoko:20201127220410p:plain


《 PostgreSQL の SQL ログ 》

[pid-1] SET TIME ZONE 'UTC'
[pid-1] SELECT "book"."id", "book"."title", "book"."price" FROM "book" WHERE "book"."id" = 1 LIMIT 21
[pid-1] BEGIN
[pid-1] INSERT INTO "order" ("status", "total_amount", "ordered_by_id", "ordered_at") VALUES ('01', 1000, 1, '2020-12-01T03:03:11.428997+00:00'::timestamptz) RETURNING "order"."id"
[pid-1] SELECT "stock"."id", "stock"."book_id", "stock"."quantity", "stock"."updated_at" FROM "stock" WHERE "stock"."book_id" = 1 LIMIT 21
[pid-1] UPDATE "stock" SET "book_id" = 1, "quantity" = 0, "updated_at" = '2020-12-01T03:03:11.442959+00:00'::timestamptz WHERE "stock"."id" = 1
[pid-1] COMMIT
[pid-1] BEGIN
[pid-1] UPDATE "order" SET "status" = '02', "total_amount" = 1000, "ordered_by_id" = 1, "ordered_at" = '2020-12-01T03:03:11.428997+00:00'::timestamptz WHERE "order"."id" = 3
[pid-1] COMMIT

「BEGIN」と「COMMIT」で囲まれたトランザクションを2つ確認することができました。




 

(おまけ)ATOMIC_REQUESTS を有効化しているときに、特定のビュー内で自前でトランザクションを切る方法

ほとんどのビューではビュー全体をひとつのトランザクションで囲めばよいが、一部のビューでのみトランザクションを細かく切らないといけないといったケースがあります。そのような場合には、ATOMIC_REQUESTS を有効にした上で、対象のビューに「django.db.transaction.non_atomic_requests」を適用して ATOMIC_REQUESTS の効果を無効化してしまえばよいです。注意点としては、Django のクラスベースビューにこれを適用するには、method_decoratorを使って dispatch() メソッドに non_atomic_requestsを適用してあげる必要があります。


参考
- データベースのトランザクション | Django ドキュメント | Django
- クラスベースビュー入門 | Django ドキュメント | Django


デコレータ以外のコードは先ほどのものと同じ内容です。

shop/views.py(ビュー)

from django.db import transaction
from django.http.response import HttpResponseRedirect
from django.shortcuts import get_object_or_404, reverse
from django.utils.decorators import method_decorator  # 追加from django.views import View

from .models import Book, BookStock, Order


@method_decorator(transaction.non_atomic_requests, name='dispatch')  # 追加classCheckoutView(View):
    ...(略)...

    defpost(self, request, *args, **kwargs):
        book = get_object_or_404(Book, pk=kwargs['pk'])

        with transaction.atomic():
            # ① 注文情報を登録
            order = Order(
                status=Order.STATUS_PAYMENT_PROCESSING,
                total_amount=book.price,
                ordered_by=request.user,
            )
            order.save()

            # ② 在庫数を確認
            book_stock = get_object_or_404(BookStock, book=book)
            # ③ 在庫数を1減らして更新
            book_stock.quantity -= 1
            book_stock.save()

        ...(決済処理)...

        with transaction.atomic():
            # ④ 注文情報のステータスを更新
            order.status = Order.STATUS_PAYMENT_OK
            order.save()

        ...(略)...

        return HttpResponseRedirect(reverse('shop:checkout_complete'))


こうすることで、特定のビュー内で ATOMIC_REQUESTS の効果を無効にして、自前でトランザクションを切ることができます。

[pid-1] SET TIME ZONE 'UTC'
[pid-1] SELECT "book"."id", "book"."title", "book"."price", "book"."updated_at" FROM "book" WHERE "book"."id" = 1 LIMIT 21
[pid-1] SELECT "stock"."id", "stock"."book_id", "stock"."quantity", "stock"."updated_at" FROM "stock" WHERE "stock"."book_id" = 1 LIMIT 21
[pid-1] BEGIN
[pid-1] UPDATE "stock" SET "book_id" = 1, "quantity" = 0, "updated_at" = '2020-12-01T03:06:00.940420+00:00'::timestamptz WHERE "stock"."id" = 1
[pid-1] INSERT INTO "order" ("status", "total_amount", "ordered_by_id", "ordered_at") VALUES ('01', 1000, 1, '2020-12-01T03:06:00.945405+00:00'::timestamptz) RETURNING "order"."id"
[pid-1] COMMIT
[pid-1] BEGIN
[pid-1] UPDATE "order" SET "status" = '02', "total_amount" = 1000, "ordered_by_id" = 1, "ordered_at" = '2020-12-01T03:06:00.945405+00:00'::timestamptz WHERE "order"."id" = 4
[pid-1] COMMIT

 

まとめ

Django はデフォルトではトランザクションを利用しておらず、すべてのクエリ操作が逐一コミットされます。Django でトランザクションを利用するには、大きく「ATOMIC_REQUESTS を有効化する」方法と「transaction.atomic() で囲む」方法の二つがあります。前者は導入が超簡単ですが、性能面での影響を考慮する必要があります。


ちなみに Django が提供しているトランザクションの API は今回紹介したものだけではありません。詳しい説明については 公式ドキュメントの後半部分をお読みください。なお、DjangoCongress JP 2019 の Denzow さんの「どうなってるの?Djangoのトランザクション」の資料が分かりやすいのでぜひ合わせて参照ください。


speakerdeck.com



 

宣伝

Django の技術同人誌をこれまでに4冊出しました。開発のお供にどうぞ。



現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文180ページ。


★ Amazon(電子版/ペーパー版)


★ BOOTH(ペーパー版)



現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文204ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django 管理サイトのつくり方

Django の管理サイト(Django Admin)だけに特化した、ニッチでオンリーワンな一冊。管理サイトをカスタマイズする前に絶対に読んでほしい本です。B5・本文152ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)

*1:https://docs.djangoproject.com/ja/3.1/topics/db/transactions/#django-s-default-transaction-behavior

*2:https://docs.djangoproject.com/ja/3.1/topics/db/transactions/#managing-database-transactions

*3:https://docs.djangoproject.com/ja/3.1/topics/db/transactions/#tying-transactions-to-http-requests

*4:なお、決済処理以降でエラーが出た場合に1つ目のトランザクションの処理を手動でロールバックする必要がありますが、それらについては割愛します。

Django で楽観的排他制御を簡単に実装する方法(初級者向け)

$
0
0

この投稿は 「Django Advent Calendar 2020 - Qiita」 4日目の記事です。


akiyoko です。
この記事では、Django で簡単に楽観的排他制御を実装する方法について説明します。Django の楽観的排他制御については拙著『現場で使える Django REST Framework の教科書』の第11章「現場で使える Tips 集」の中でも触れていますが、もう少し詳しく解説してみたいと思います。


f:id:akiyoko:20201204095634p:plain



検証環境
  • Windows 10 Home
  • Django 3.1
  • PostgreSQL 13.1



 

はじめに

複数ユーザからほぼ同じタイミングで同じレコードに対して更新をおこなうと、タイミングによってはレースコンディション(競合状態)が発生してしまい、更新内容が上書きされてしまうことがあります。

次の図を見てください。

f:id:akiyoko:20201127234524p:plain


一例として、決済処理の中で在庫テーブルを 1 ずつ減らして更新する処理を抜き出したものです。Aさんが在庫テーブルを参照(①)して更新(③)するまでのわずかな時間の中に、Bさんからの在庫テーブルの参照(②)が挟まっている状況を示しています。AさんとBさんが在庫テーブルを参照した時点(① および ②)では双方とも在庫数が「1」となっているのですが、Aさんが在庫テーブルを更新した③のタイミングで在庫数が「0」になるのにも関わらず、Bさんがレコードを「0」に更新(すなわち上書き)できてしまっています。なんと、在庫が「1」だったにも関わらず、二回も購入ができてしまっているのです。


これがレースコンディション(競合状態)です。解決方法としては「排他制御」と呼ばれる対策が採られることが多いです。中でも、更新時にデータが最新のものかどうかをチェックして、データが最新でない場合にエラーを出してレコードを更新しないようにする方式のものを特に「楽観的排他制御」と呼びます。



楽観的排他制御と悲観的排他制御

排他制御には、「楽観的排他制御」(楽観ロック)と「悲観的排他制御」(悲観ロック)があります。特徴や違いについては参考記事に譲ります。


参考


引用してまとめるとこうなります。



楽観的排他制御

  • 更新対象のデータがデータ取得時と同じ状態であることを確認してから更新することで、データの整合性を保証する方式
  • データ取得時の最終更新日時またはバージョン番号を条件に含めて更新する
  • 同時更新されることがあまりない場合に使う

悲観的排他制御

  • 更新対象のデータを取得する際にロックをかけることで、他のトランザクションから更新されないようにする方式
  • 「SELECT FOR UPDATE」を使う
  • 同時更新されることが多く、トランザクションが短い場合に使う


 
Django でも「SELECT ... FOR UPDATE」を使うことができますが、本記事では比較的実装が簡単な「楽観的排他制御」の具体的な実装方法について説明します。


次に、データベースのトランザクション分離レベル(isolation level)の話をします。



 

READ COMMITTED の挙動

Django においては、データベースのトランザクション分離レベルは「READ COMMITTED」がベストとされていて、データベース接続設定のデフォルトのトランザクション分離レベルも「READ COMMITTED」となっています。*1


ちなみに、Django 2.0 以降、MySQL をバックエンドにした場合の分離レベルのデフォルト設定も「READ COMMITTED」に変更になりました。MySQL(InnoDB)自体のデフォルトのトランザクション分離レベルは「REPEATABLE READ」ですが、もしそれに合わせたい場合は、Django 側のデータベースオプションで明示的に

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        ...
        'OPTIONS': {
            ...
            'isolation_level': 'REPEATABLE READ',
        }
    }
}

として、「isolation_level」の指定をしないといけません。



「READ COMMITTED」のトランザクション内では、「REPEATABLE READ」とは異なり、他のトランザクションのコミットによる変更が参照できます。例えば、次の図のように、他のユーザのトランザクションが終了した瞬間から最新の値が参照できるようになっています。


f:id:akiyoko:20201129144914p:plain


つまり、「READ COMMITTED」では、更新するギリギリのタイミングで最新のデータを参照することでレースコンディション(競合状態)を起こしにくくすることができると言えるでしょう。


これを踏まえた上で、楽観的排他制御の具体的な実装方法としては、例えばモデルにバージョンを保持するフィールドを追加し、データが更新されるたびに値をインクリメントして更新するようにします。そして更新の際にバージョンの値を検索条件に含め、更新件数が0件となった場合に排他エラーを発生させます。このような仕組みを自前で実装してもよいのですが、django-concurrencyパッケージを利用すると簡単に実装することができます。



 

django-concurrency パッケージを利用する

django-concurrencyパッケージを利用すると、楽観的排他制御が簡単に実現できます。

まず、pip で django-concurrency をインストールします。

(venv) > pip install django-concurrency==2.2.*


あとは、排他制御したいモデルに次のように concurrency.fields.AutoIncVersionField を使って「version」フィールドを追加するだけです。


shop/models.py(モデル)

from concurrency.fields import AutoIncVersionField  # 追加from django.db import models


classBook(models.Model):
    """本モデル"""classMeta:
        db_table = 'book'
        verbose_name = verbose_name_plural = '本'

    title = models.CharField('タイトル', max_length=255, unique=True)
    price = models.PositiveIntegerField('価格', null=True, blank=True)

    def__str__(self):
        return self.title


classBookStock(models.Model):
    """在庫モデル"""classMeta:
        db_table = 'stock'
        verbose_name = verbose_name_plural = '在庫'

    book = models.OneToOneField(Book, verbose_name='本', on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField('在庫数', default=0)
    updated_at = models.DateTimeField('更新日時', auto_now=True)
    version = AutoIncVersionField(verbose_name='バージョン')   # 追加def__str__(self):
        return f'{self.book.title} ({self.quantity})'


AutoIncVersionField は初回登録時に初期値「1」で自動保存され、更新するたびに自動でインクリメントされていきます。以下は Django シェル環境での実行例です。

>>> s1 = BookStock.objects.create(book_id=1, quantity=10)
>>> s1.version
1
>>> s1.save()
>>> s1.version
2

レコードの更新時にはバージョンの値が更新条件に自動的に加えられ、条件に合致しない場合は更新件数が0件になり、RecordModifiedError が発生します。

>>> s2 = BookStock.objects.get(book_id=1)
>>> s2.version
2
>>> s1.save()
>>> s1.version
3
>>> s2.save()
Traceback (most recent call last):
  ...(略)...
concurrency.exceptions.RecordModifiedError: Record has been modified


先ほどの決済処理の例では次のようにBさんの処理がエラーになり、更新ができなくなります。

f:id:akiyoko:20201127234543p:plain


「ATOMIC_REQUESTS」や「transaction.atomic() 」でトランザクションを利用していて、RecordModifiedError がトランザクション内でキャッチされなかった場合はトランザクションがロールバックされます。


PostgreSQL の SQL ログの例は次のようになります(「pid-1」および「pid2」はプロセス番号を分かりやすいように書き換えたものです)。

《 PostgreSQL の SQL ログ 》

12:00:11.976 JST [pid-1] SET TIME ZONE 'UTC'
12:00:11.980 JST [pid-1] SELECT "book"."id", "book"."title", "book"."price" FROM "book" WHERE "book"."id" = 1 LIMIT 21
12:00:11.984 JST [pid-1] BEGIN
12:00:11.986 JST [pid-1] INSERT INTO "order" ("status", "total_amount", "ordered_by_id", "ordered_at") VALUES ('01', 1000, 1, '2020-12-01T03:00:11.983496+00:00'::timestamptz) RETURNING "order"."id"
12:00:11.992 JST [pid-1] SELECT "stock"."id", "stock"."book_id", "stock"."quantity", "stock"."updated_at", "stock"."version" FROM "stock" WHERE "stock"."book_id" = 1 LIMIT 21

12:00:12.011 JST [pid-2] SET TIME ZONE 'UTC'
12:00:12.013 JST [pid-2] SELECT "book"."id", "book"."title", "book"."price" FROM "book" WHERE "book"."id" = 1 LIMIT 21
12:00:12.019 JST [pid-2] BEGIN
12:00:12.020 JST [pid-2] INSERT INTO "order" ("status", "total_amount", "ordered_by_id", "ordered_at") VALUES ('01', 1000, 1, '2020-12-01T03:00:12.016340+00:00'::timestamptz) RETURNING "order"."id"
12:00:12.028 JST [pid-2] SELECT "stock"."id", "stock"."book_id", "stock"."quantity", "stock"."updated_at", "stock"."version" FROM "stock" WHERE "stock"."book_id" = 1 LIMIT 21

12:00:13.109 JST [pid-1] SELECT (1) AS "a" FROM "stock" WHERE "stock"."id" = 1 LIMIT 1
12:00:13.112 JST [pid-1] UPDATE "stock" SET "book_id" = 1, "quantity" = 0, "updated_at" = '2020-12-01T03:00:13.106776+00:00'::timestamptz, "version" = 2 WHERE ("stock"."id" = 1 AND "stock"."version" = 1)
12:00:13.115 JST [pid-1] COMMIT
12:00:13.124 JST [pid-1] BEGIN
12:00:13.126 JST [pid-1] UPDATE "order" SET "status" = '02', "total_amount" = 1000, "ordered_by_id" = 1, "ordered_at" = '2020-12-01T03:00:11.983496+00:00'::timestamptz WHERE "order"."id" = 55
12:00:13.128 JST [pid-1] COMMIT

12:00:14.412 JST [pid-2] SELECT (1) AS "a" FROM "stock" WHERE "stock"."id" = 1 LIMIT 1
12:00:14.417 JST [pid-2] UPDATE "stock" SET "book_id" = 1, "quantity" = 0, "updated_at" = '2020-12-01T03:00:14.409133+00:00'::timestamptz, "version" = 2 WHERE ("stock"."id" = 1 AND "stock"."version" = 1)
12:00:14.419 JST [pid-2] ROLLBACK

UPDATE 時に version の値が検索条件として加えられているのが確認できます。「pid-2」の UPDATE 時にRecordModifiedError が発生し、トランザクションがロールバックされています。




django-concurrency の「concurrency.middleware.ConcurrencyMiddleware」というミドルウェアを使うことで、独自の排他エラー画面に遷移させることも可能です。

config/settings.py(設定ファイル)

MIDDLEWARE = [
    'concurrency.middleware.ConcurrencyMiddleware',  # 追加'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',
]

ConcurrencyMiddleware には ビューから例外が漏れたとき実行される process_exception が実装されていて、例外クラスが RecordModifiedError の場合に「concurrency.views.conflict」というコールバックが呼ばれ、最終的に「409.html」という名前のテンプレートがレンダリングされるような作りになっています。


そこで、設定ファイルの「TEMPLATES」の設定を次のように修正した上で、

config/settings.py(設定ファイル)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # 修正'APP_DIRS': True,
        ...(略)...
    },
]

BASE_DIR の直下の「templates」ディレクトリの下に 409.html を配置しておくと、排他エラー発生時にこの画面に遷移させることが可能です(templates/base.html のコードは省略)。

templates/409.html

{% extends "base.html" %}

{% block title %}排他エラー{% endblock %}

{% block content %}
<h3>排他エラー</h3><hr><p>排他エラーが発生しました。</p>
{% endblock %}


f:id:akiyoko:20201128155216p:plain:w400


排他制御エラー発生時のコールバック関数をカスタマイズすることも可能です。公式ドキュメントに書かれている通り、設定ファイルに「CONCURRENCY_HANDLER409」を定義します。


config/settings.py(設定ファイル)

MIDDLEWARE = [
    'concurrency.middleware.ConcurrencyMiddleware',
    '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',
]

CONCURRENCY_HANDLER409 = 'common.handlers.conflict'# 追加


common/handlers.py

from django.shortcuts import render

from shop.models import BookStock


defconflict(request, target=None, template_name='409.html'):
    ifisinstance(target, BookStock):
        template_name = 'shop/book_stock_conflict.html'try:
        saved = target.__class__._default_manager.get(pk=target.pk)
    except target.__class__.DoesNotExists:
        saved = None

    context = {
        'target': target,  # 更新しようとしたモデルオブジェクト'saved': saved,  # 最新状態のモデルオブジェクト
    }
    return render(request, template_name, context)


templates/shop/book_stock_conflict.html

{% extends "base.html" %}

{% block title %}排他エラー{% endblock %}

{% block content %}
<h3>排他エラー</h3><hr><p>在庫レコードの更新時に排他エラーが発生しました。</p>
{% endblock %}


f:id:akiyoko:20201128181553p:plain:w400



なお、disable_concurrencyをデコレータや with 句で利用することで、特定のビューやモデル操作に対して排他制御をしないようにすることもできます。またこの django-concurrency は、管理サイトや Django REST Framework(DRF)でも利用できるので大変便利です。



 

まとめ

複数ユーザからほぼ同じタイミングで同じレコードに対して更新をおこなうと、タイミングによってはレースコンディション(競合状態)が発生し、更新内容が上書きされてしまうことがあります。レースコンディションが業務にクリティカルな影響を与えてしまう場合は排他制御が必要になります。

Django で楽観的排他制御を実現するには、django-concurrency パッケージを利用すれば非常に簡単に実装することができます。



 

宣伝

Django の技術同人誌をこれまでに4冊出しました。開発のお供にどうぞ。



現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文180ページ。


★ Amazon(電子版/ペーパー版)


★ BOOTH(ペーパー版)



現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文204ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django 管理サイトのつくり方

Django の管理サイト(Django Admin)だけに特化した、ニッチでオンリーワンな一冊。管理サイトをカスタマイズする前に絶対に読んでほしい本です。B5・本文152ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)

Django でデータベースビューを扱う方法(初級者向け)

$
0
0

この投稿は 「Django Advent Calendar 2020 - Qiita」 5日目の記事です。


akiyoko です。
この記事では、Django でデータベースビューを扱う方法について説明します。「Django のコンポーネントとしてのビュー(View)」ではなく、「データベースのビュー」の話です。本記事では区別のために「データベースビュー」と表記することにします。



f:id:akiyoko:20201205082412p:plain:w350



検証環境
  • Windows 10 Home
  • Django 3.1
  • PostgreSQL 13.1



 

はじめに

あまり知られていないかもしれませんが、Django でもデータベースビューを扱うことができます。具体的には、データベースビューに対応するモデルを用意することで、データベースビューのレコードをモデルオブジェクトとして取り扱うことができます(しかし当然ながら、データベースビューへのレコードの登録や更新、削除はできませんのでご注意を)。



ところで、データベースビューを使うと何が嬉しいのでしょうか?

例えば、テーブルの結果を 集計したり、サブクエリを使ったり、複数のテーブルを UNIONしたりするには、Django ORM の API を使って実現できないこともありませんが、コードが複雑になり、思わぬバグが混入する可能性が高くなってしまいます。それならいっそのこと SQL で書いてしまう方が早くて確実だというケースもあるでしょう。そんなときにデータベースビューを使えば、モデル側のコードをすっきりさせることができるのです。 *1




 

データベースビューに対応するモデル

通常のモデルとの違いは、Meta に「managed = False」を指定するだけです。これで、モデルをマイグレーションの対象外にすることができます。*2


次の例を見てください。

sales/models.py(モデル)

from django.db import models


classSalesResultPerMonth(models.Model):
    classMeta:
        managed = False
        db_table = 'sales_result_per_month'
        verbose_name = verbose_name_plural = '月別合計売上金額'

    sales_month = models.DateField('売上月')
    total_amount = models.PositiveIntegerField('合計売上金額')
    target_amount = models.PositiveIntegerField('目標売上金額')


モデルの Meta クラスに「managed = False」を指定しています。ちなみにデータベースビューに対応したモデルには「null=True」や「blank=True」などのフィールドオプションは付ける必要はありません。データベースビューには登録や更新ができないため、付けてもあまり意味がないからです。



モデルをマイグレーションの対象外にすると、マイグレーション(migrate)コマンドを実行したときにマイグレーションファイルの内容がデータベースのテーブルに反映されず、テーブルが新たに作成されたり、テーブル構造が変更されたりすることがなくなります。


f:id:akiyoko:20201205100932p:plain:w550


 

具体例

簡単な例として、売上実績テーブルから月次の売上金額の合計を集計して、売上目標テーブルの売上目標金額と比較することを取り上げてみます。これをデータベースビューとして利用するイメージは次のようになります。


f:id:akiyoko:20201201094402p:plain

通常のモデルを作成する

まず、sales アプリケーションに「売上目標モデル」と「売上実績モデル」を次のように定義します。これらはマイグレーション対象となる通常のモデルです。

sales/models.py(モデル)

from django.db import models


classSalesTarget(models.Model):
    """売上目標モデル"""classMeta:
        db_table = 'sales_target'
        verbose_name = verbose_name_plural = '売上目標'

    sales_month = models.DateField('売上月')
    amount = models.PositiveIntegerField('目標金額')
    created_at = models.DateTimeField('登録日時', auto_now_add=True)

    def__str__(self):
        return f'{self.sales_month:%Y年%m月}'classSalesResult(models.Model):
    """売上実績モデル"""classMeta:
        db_table = 'sales_result'
        verbose_name = verbose_name_plural = '売上実績'

    sales_date = models.DateField('売上日')
    amount = models.PositiveIntegerField('売上金額')
    subject = models.CharField('件名', max_length=255)
    created_at = models.DateTimeField('登録日時', auto_now_add=True)

    def__str__(self):
        return f'{self.sales_date:%Y年%m月%d日} - {self.subject}'


makemigrations コマンドでマイグレーションファイルを作成して、migrate コマンドでマイグレーションを実行すると、次のようなテーブルが作成されます。

(venv) > python manage.py makemigrations sales
(venv) > python manage.py migrate sales


f:id:akiyoko:20201201094218p:plain

データベースビュー作成用の DDL をマイグレーションファイルに書く

データベースビューを作成するための DDL は、マイグレーションファイルに書くのがよいでしょう。次のように「--empty」オプションを付けて makemigrations コマンドを実行すると、空のマイグレーションファイルを作成することができます。

(venv) $ python manage.py makemigrations sales --empty


上記のコマンドを実行すると、次のようなマイグレーションファイルが「sales/migrations」ディレクトリの下に生成されます。


sales/migrations/0002_auto_20201204_2134.py(マイグレーションファイル)

# Generated by Django 3.1.3 on 2020-12-04 12:34from django.db import migrations


classMigration(migrations.Migration):

    dependencies = [
        ('sales', '0001_initial'),
    ]

    operations = [
    ]


このひな型ファイルを次のように編集して、sales_result_per_month ビューを作成する DDL を書き加えます。

# Generated by Django 3.1.3 on 2020-12-04 12:34from django.db import migrations


classMigration(migrations.Migration):

    dependencies = [
        ('sales', '0001_initial'),
    ]

    sql = """        CREATE VIEW sales_result_per_month AS            SELECT                ROW_NUMBER() OVER() AS id,                t.sales_month,                t.amount AS target_amount,                SUM(r.amount) AS total_amount            FROM                sales_target t            LEFT OUTER JOIN                sales_result r            ON                TO_CHAR(t.sales_month, 'YYYY-MM') = TO_CHAR(r.sales_date, 'YYYY-MM')            GROUP BY                t.sales_month, t.amount            ORDER BY                t.sales_month;"""

    reverse_sql = """        DROP VIEW IF EXISTS sales_result_per_month;"""

    operations = [
        migrations.RunSQL(sql, reverse_sql),
    ]

RunSQL の第一引数には migrate コマンドが実行されたときに発行する SQL を、第二引数には特定のバージョンのマイグレーションの状態に戻す *3ときに発行する SQL を指定します(第二引数は省略可)。ちなみにこの SQL は PostgreSQL 向けのものです。


マイグレーションファイルを手動で書くやり方については、次の記事を参考にしてみてください。

参考



マイグレーションを実行すると、データベースビューが作成されます。

(venv) $ python manage.py migrate sales


pgAdmin 4 上で確認すると、このようになっています。


f:id:akiyoko:20201201114913p:plain


ビューに対応するモデルを作成する

sales_result_per_month ビューに対応するモデルを作成します。ここで Meta クラスに「managed = False」を指定します。繰り返しになりますが、これでマイグレーションの対象から外れます。


sales/models.py(モデル)

from django.db import models


classSalesTarget(models.Model):
    """売上目標モデル"""
    ...(略)...


classSalesResult(models.Model):
    """売上実績モデル"""
    ...(略)...


classSalesResultPerMonth(models.Model):  # 追加"""月別合計売上金額モデル"""classMeta:
        managed = False
        db_table = 'sales_result_per_month'
        verbose_name = verbose_name_plural = '月別合計売上金額'

    sales_month = models.DateField('売上月')
    total_amount = models.PositiveIntegerField('合計売上金額')
    target_amount = models.PositiveIntegerField('目標売上金額')



モデルを作成する際には、次のように

(venv) > python manage.py inspectdb sales_result_per_month

inspectdbという既存のテーブルからモデルを自動生成する Django コマンドを実行した結果を参考にするとよいでしょう。


(出力結果例)

# This is an auto-generated Django model module.# You'll have to do the following manually to clean this up:#   * Rearrange models' order#   * Make sure each model has one field with primary_key=True#   * Make sure each ForeignKey and OneToOneField has `on_delete` set to the desired behavior#   * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table# Feel free to rename the models, but don't rename db_table values or field names.from django.db import models


classSalesResultPerMonth(models.Model):
    id = models.BigIntegerField(blank=True, null=True)
    sales_month = models.DateField(blank=True, null=True)
    target_amount = models.IntegerField(blank=True, null=True)
    total_amount = models.BigIntegerField(blank=True, null=True)

    classMeta:
        managed = False# Created from a view. Don't remove.
        db_table = 'sales_result_per_month'



 

管理サイトでデータベースビューの内容を確認する

最後に、このデータベースビューの内容を管理サイト上で確認できるようにしてみましょう。sales/admin.py を次のように編集します。


sales/admin.py(管理サイト用モジュール)

from django.contrib import admin

from common.helpers.admin_helper import format_yen, format_yyyy_nen_mm_gatsu
from .models import SalesResult, SalesResultPerMonth, SalesTarget


classSalesResultAdmin(admin.ModelAdmin):
    """売上実績モデル用 ModelAdmin"""################################ モデル一覧画面のカスタマイズ###############################
    list_display = ('sales_date', 'format_amount', 'subject')
    ordering = ('sales_date', 'created_at')

    defformat_amount(self, obj):
        return format_yen(obj.amount)

    format_amount.short_description = '売上金額'
    format_amount.admin_order_field = 'amount'################################ モデル追加・変更画面のカスタマイズ###############################
    readonly_fields = ('id', 'created_at')


classSalesTargetAdmin(admin.ModelAdmin):
    """売上目標モデル用 ModelAdmin"""################################ モデル一覧画面のカスタマイズ###############################
    list_display = ('format_sales_month', 'format_amount')
    ordering = ('sales_month',)

    defformat_sales_month(self, obj):
        return format_yyyy_nen_mm_gatsu(obj.sales_month)

    format_sales_month.short_description = '売上月'
    format_sales_month.admin_order_field = 'sales_month'defformat_amount(self, obj):
        return format_yen(obj.amount)

    format_amount.short_description = '目標金額'
    format_amount.admin_order_field = 'amount'################################ モデル追加・変更画面のカスタマイズ###############################
    readonly_fields = ('id', 'created_at')


classSalesResultPerMonthAdmin(admin.ModelAdmin):
    """月別合計売上金額モデル用 ModelAdmin"""################################ モデル一覧画面のカスタマイズ###############################
    list_display = ('format_sales_month', 'format_target_amount', 'format_total_amount')
    ordering = ('sales_month',)

    defformat_sales_month(self, obj):
        return format_yyyy_nen_mm_gatsu(obj.sales_month)

    format_sales_month.short_description = '売上月'
    format_sales_month.admin_order_field = 'sales_month'defformat_target_amount(self, obj):
        return format_yen(obj.target_amount)

    format_target_amount.short_description = '目標売上金額'
    format_target_amount.admin_order_field = 'target_amount'defformat_total_amount(self, obj):
        return format_yen(obj.total_amount)

    format_total_amount.short_description = '合計売上金額'
    format_total_amount.admin_order_field = 'total_amount'################################ その他のカスタマイズ###############################defhas_add_permission(self, request):
        returnFalsedefhas_change_permission(self, request, obj=None):
        returnFalsedefhas_delete_permission(self, request, obj=None):
        returnFalse


admin.site.register(SalesTarget, SalesTargetAdmin)
admin.site.register(SalesResult, SalesResultAdmin)
admin.site.register(SalesResultPerMonth, SalesResultPerMonthAdmin)



管理サイトの月別合計売上金額モデルの一覧画面はこのように表示されます。


f:id:akiyoko:20201202105916p:plain:w500

何がどうなっているのか?が気になる方は、拙著『現場で使える Django 管理サイトのつくり方』にいろいろと書いてあるので、ぜひお手に取ってご確認くださいませ 🙇

akiyoko.hatenablog.jp



 

(おまけ)マイグレーションファイルの SQL を複数種類のデータベースに対応させる方法

例示したマイグレーションファイルの SQL は PostgreSQL だけにしか使えません。PostgreSQL を使うだけならこれでもよいのですが、ローカルでは SQLite を使って、本番環境では PostgreSQL を使って・・といったケースには対応できません。そのような場合は、RunPython を使って次のように書き分けることができます。

# Generated by Django 3.1.3 on 2020-12-04 12:34from django.db import migrations
from django.db.migrations import exceptions


defcode(apps, schema_editor):
    if schema_editor.connection.vendor == 'sqlite':
        schema_editor.execute("""            CREATE VIEW sales_result_per_month AS                SELECT                    ROW_NUMBER() OVER() AS id,                    t.sales_month,                    t.amount AS target_amount,                    SUM(r.amount) AS total_amount                FROM                    sales_target t                LEFT OUTER JOIN                    sales_result r                ON                    STRFTIME('%Y-%m', t.sales_month) = STRFTIME('%Y-%m', r.sales_date)                GROUP BY                    t.sales_month                ORDER BY                    sales_month;""")
    elif schema_editor.connection.vendor == 'postgresql':
        schema_editor.execute("""            CREATE VIEW sales_result_per_month AS                SELECT                    ROW_NUMBER() OVER() AS id,                    t.sales_month,                    t.amount AS target_amount,                    SUM(r.amount) AS total_amount                FROM                    sales_target t                LEFT OUTER JOIN                    sales_result r                ON                    TO_CHAR(t.sales_month, 'YYYY-MM') = TO_CHAR(r.sales_date, 'YYYY-MM')                GROUP BY                    t.sales_month, t.amount                ORDER BY                    t.sales_month;""")
    elif schema_editor.connection.vendor == 'mysql':
        schema_editor.execute("""            CREATE VIEW sales_result_per_month AS                SELECT                    ROW_NUMBER() OVER() AS id,                    t.sales_month,                    t.amount AS target_amount,                    SUM(r.amount) AS total_amount                FROM                    sales_target t                LEFT OUTER JOIN                    sales_result r                ON                    DATE_FORMAT(t.sales_month, '%Y-%m') = DATE_FORMAT(r.sales_date, '%Y-%m')                GROUP BY                    t.sales_month;                ORDER BY                    t.sales_month;""", params=None)
    else:
        raise exceptions.BadMigrationError(
            'Database vendor should be SQLite, PostgreSQL, or MySQL.')


defreverse_code(apps, schema_editor):
    schema_editor.execute('DROP VIEW IF EXISTS sales_result_per_month;')


classMigration(migrations.Migration):
    dependencies = [
        ('sales', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(code, reverse_code, atomic=False)
    ]


 

まとめ

モデルの Meta クラスに「managed = False」を指定すると、マイグレーションの対象から外すことができるため、データベースビューに対応するモデルを作成することができます。


またこの方法を利用すれば、すでにあるテーブルに対応するモデルを作成することも可能です。いろいろ便利に使えそうですね!



 

宣伝

Django の技術同人誌をこれまでに4冊出しました。開発のお供にどうぞ。



現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文180ページ。


★ Amazon(電子版/ペーパー版)


★ BOOTH(ペーパー版)



現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文204ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django 管理サイトのつくり方

Django の管理サイト(Django Admin)だけに特化した、ニッチでオンリーワンな一冊。管理サイトをカスタマイズする前に絶対に読んでほしい本です。B5・本文152ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)

*1:モデルマネージャの raw() や django.db.connection.cursor オブジェクトの execute() を使えば、生の SQL を書くことも可能です。

*2:https://docs.djangoproject.com/ja/3.1/ref/models/options/#managed

*3:マイグレーション | Django ドキュメント | Django

Django REST Framework で API ドキュメンテーション機能を利用する方法(DRF 3.12 最新版)

$
0
0

この投稿は 「Django Advent Calendar 2020 - Qiita」 8日目の記事です。


akiyoko です。
この記事では、Django REST Framework(通称「DRF」)で API スキーマを自動生成する「API ドキュメンテーション」機能を簡単に利用する方法について説明します。


f:id:akiyoko:20201208093319p:plain:w600



検証環境
  • Windows 10 Home
  • Django 3.1
  • Django REST Framework 3.12



 

はじめに

Django REST Framework には、API スキーマ(どのような URL にどのようなリクエストを送ればどのようなレスポンスが返ってくるかという API の定義を記述したもの)を出力してくれる「API ドキュメンテーション」機能が備わっています。



DRF 公式ドキュメント


API スキーマの仕様は「OpenAPI 3.0」に準拠しています。「OpenAPI 3.0」は、REST API インタフェース記述の標準化を目指して Swagger 2.0 を統合して策定された仕様です。

swagger.io


OpenAPI スキーマファイル(YAML形式)の例を次に示します。

openapi:"3.0.0"info:title: Simple API overview
  version: 2.0.0
paths:/:get:operationId: listVersionsv2
      summary: List API versions
      responses:'200':description: |-
            200 response
          content:application/json:examples:foo:value:{"versions":[{"status":"CURRENT",
                            "updated":"2011-01-21T11:33:21Z",
                            "id":"v2.0",
                            "links":[{"href":"http://127.0.0.1:8774/v2/",
                                    "rel":"self"}]},
                        {"status":"EXPERIMENTAL",
                            "updated":"2013-07-23T11:33:21Z",
                            "id":"v3.0",
                            "links":[{"href":"http://127.0.0.1:8774/v3/",
                                    "rel":"self"}]}]}

...(略)...

https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/api-with-examples.yamlより引用



 

DRF 3.12 の状況

現在の最新バージョンの DRF 3.12で OpenAPI 3.0 に準拠した API スキーマを出力する方法として、筆者は次の二つの方法を推奨します。ひとつは DRF 標準の generateschema コマンドを使って OpenAPIスキーマファイルを出力する方法、もうひとつは 「drf-spectacular」というサードパーティ製パッケージを使う方法です。*1



ちなみに、DRF で API スキーマを出力するにはこれまで「CoreAPI」ベースの「rest_framework.schemas.coreapi.AutoSchema」を使うのがデフォルトだったのですが、これは DRF 3.10 で非推奨になり、DRF 3.12 以降で削除予定となっています。*2
なおこの記事を書いている時点ではまだ削除はされていないようですが、今後いつ削除されるとも分かりません。

Django REST Framework v3.10 でネイティブな OpenAPI ベースのスキーマ生成が導入されたことにより、CoreAPI ベースのスキーマの使用は非推奨となりました。

Django REST Framework v3.12 で CoreAPI 関連のコードはすべて削除されます。それまでに OpenAPI スキーマに切り替えてください。

https://www.django-rest-framework.org/coreapi/の内容を抜粋して翻訳)



本記事では、DRF 3.12 で OpenAPI 3.0 に準拠した API スキーマを出力する方法として

  • 【方法1】generateschema コマンドを使って OpenAPIスキーマファイルを出力する方法
  • 【方法2】drf-spectacular を利用する方法

について紹介します。


 

【方法1】DRF 標準の generateschema コマンドを使ってスキーマファイルを出力する

DRF 標準の generateschema コマンドを使って OpenAPI スキーマファイルを出力するには、PyYAML パッケージと uritemplate パッケージが必要です。


そこで、次のようにして PyYAML と uritemplate をそれぞれインストールします。

(venv) > pip install PyYAML==5.3.* uritemplate==3.0.*


次のように generateschema コマンドを実行すれば、OpenAPI 3.0 に対応した YAML形式のスキーマファイルを出力することができます。ちなみに format オプションで「openapi-json」を指定すれば、JSON 形式のファイルを出力することも可能です。

(venv) > python manage.py generateschema --file schema.yml


コマンドを実行すると、次のようなスキーマファイルが出力されます。

openapi: 3.0.2
info:title:''version:''paths:/api/v0/books/:get:operationId: listBookListCreates
      description:"\u672C\u30E2\u30C7\u30EB\u306E\u53D6\u5F97\uFF08\u4E00\u89A7\uFF09\        API\u306B\u5BFE\u5FDC\u3059\u308B\u30CF\u30F3\u30C9\u30E9\u30E1\u30BD\u30C3\
        \u30C9"
      parameters:[]responses:'200':content:application/json:schema:type: array
                items:{}description:''tags:- api

      ...(略)...



出力したスキーマファイルを、OpenAPI に対応した Swagger EditorSwagger Inspectorなどのツールに読み込ませて、ツール上で API の定義を確認したり、API クライアント(API へのリクエストを実行できるツール)として利用したりすることができます。



オンライン API ドキュメントエディタ Swagger Editor の画面例


f:id:akiyoko:20201202234527p:plain:w500

Swagger Inspector の画面例


f:id:akiyoko:20201202234559p:plain:w500

このほかにも OpenAPI スキーマファイルが利用できるツールは多数存在します。

openapi.tools


公式ドキュメントでは、URLconf やテンプレートの準備をして Swagger UI と ReDoc の二種類の API ドキュメント画面を表示させる方法を紹介していますが、次に示す drf-spectacularを利用する方が簡単なので筆者はそちらを推します(公式ドキュメントでも代替案として drf-spectacular を紹介しています)。



 

【方法2】drf-spectacular を利用する

これまで DRF で標準になっていた CoreAPI ベースの「AutoSchema」を使えば、次のような API ドキュメント画面を簡単に構築することができました。*3


f:id:akiyoko:20201204000317p:plain:w450

しかし先述のように、CoreAPI ベースの「AutoSchema」の利用は非推奨になってしまいました。そこで、代わりに「drf-spectacular」を利用すれば、OpenAPI 3.0 準拠の API スキーマファイルを出力できるのに加えて、Swagger UI 形式と ReDoc 形式の二種類の API ドキュメント画面を自動生成することができます。

 

導入方法

導入方法はほぼ 公式ドキュメントに書いてある通りです。


まず、drf-spectacular をインストールします。*4

(venv) > pip install drf-spectacular==0.11.*


次に、設定ファイルの「INSTALLED_APPS」に「drf_spectacular」を追加し、「REST_FRAMEWORK」の「DEFAULT_SCHEMA_CLASS」に「drf_spectacular.openapi.AutoSchema」を設定します。


config/settings.py(設定ファイル)

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 3rd party apps'drf_spectacular',  # 追加

    ...
]

...

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',  # 追加
}


OpenAPI スキーマファイルを出力するだけなら設定はここまでで OK です。コマンドプロンプトから spectacular コマンドを使えば、API スキーマファイルを出力することができます。

(venv) > python manage.py spectacular --file schema.yml



APIドキュメント画面を自動生成したいのであれば、続けて URLconf に次の設定を書き加えます。ちなみに、DEBUG が False の場合にのみ APIドキュメント画面を表示できるようにしています。


config/urls.py(URLconf)

from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView  # 追加

urlpatterns = [
    ...(略)...
]

if settings.DEBUG:
    urlpatterns += [
        path('api/schema/', SpectacularAPIView.as_view(), name='schema'),                                      # 追加
        path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),  # 追加
        path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),              # 追加
    ]


 

Swagger UI 形式の APIドキュメント画面

設定完了後に runserver を起動し、(起動 URL が「127.0.0.1:8000」の場合)ブラウザで「http://127.0.0.1:8000/api/schema/swagger-ui/」にアクセスすれば、Swagger UI 形式の APIドキュメント画面が表示できます。


f:id:akiyoko:20201202231159p:plain:w550

API ごとの「Try it out」ボタンをクリックして、「Parameters」を適宜入力して「Execute」ボタンをクリックすることでリクエストを送信することができるので、API クライアントとしても利用することができます。



 

ReDoc 形式の APIドキュメント画面

ReDoc 形式の APIドキュメント画面(http://127.0.0.1:8000/api/schema/redoc/)は次のようになります。


f:id:akiyoko:20201202232040p:plain:w550



 

カスタマイズ

これでよいかと思いきや、ちょっとしたカスタマイズが必要な箇所もあります。

例えば、ListAPIView や RetrieveAPIView などの汎用 APIView 系ビューや ModelViewSet 系ビューなど(クラス変数 serializer_class を指定するもの)は入出力のパラメータが自動判定されるのですが、APIView を継承しているビューではドキュメンテーションが不完全になります。その場合は、extend_schema でドキュメンテーション用の追加設定をしてあげる必要があります。


apiv0/views.py(ビュー)

from drf_spectacular.utils import extend_schema
from rest_framework import status, views
from rest_framework.response import Response

from shop.models import Book
from .serializers import BookSerializer


classBookListCreateAPIView(views.APIView):
    """本モデルの取得(一覧)・登録APIクラス"""@extend_schema(
        responses={200: BookSerializer},
    )
    defget(self, request, *args, **kwargs):
        """本モデルの取得(一覧)APIに対応するハンドラメソッド"""

        ...(略)...

        return Response(serializer.data, status.HTTP_200_OK)

    @extend_schema(
        request=BookSerializer,
        responses={201: BookSerializer},
    )
    defpost(self, request, *args, **kwargs):
        """本モデルの登録APIに対応するハンドラメソッド"""

        ...(略)...

        return Response(serializer.data, status.HTTP_201_CREATED)


他にもいろいろ追加設定ができますが、詳細については公式ドキュメントを参照してください。

Settings — drf-spectacular documentation





 

まとめ

Django REST Framework(DRF)にはデフォルトで OpenAPI 3.0 に対応した API スキーマを出力してくれる「API ドキュメンテーション」機能が備わっています。利用方法は簡単で、generateschema コマンドでスキーマファイルを出力するだけです(ただし、PyYAML と uritemplate のインストールが必要)。


API スキーマをリアルタイムに反映した API ドキュメント画面を表示したいのであれば、drf-spectacular パッケージを利用するのが簡単です。なお、DRF 3.12 以降は、DRF 3.10 までで標準だった CoreAPI ベースの APIドキュメント画面が利用できなくなるので注意が必要です。



 

宣伝

Django の技術同人誌をこれまでに4冊出しました。開発のお供にどうぞ。



現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文180ページ。


★ Amazon(電子版/ペーパー版)


★ BOOTH(ペーパー版)



現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文204ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)



現場で使える Django 管理サイトのつくり方

Django の管理サイト(Django Admin)だけに特化した、ニッチでオンリーワンな一冊。管理サイトをカスタマイズする前に絶対に読んでほしい本です。B5・本文152ページ。


★ Amazon(電子版)


★ BOOTH(ペーパー版)

*1:サードパーティ製パッケージとしては「drf-yasg」も有名ですが、READMEにも書かれている通り、OpenAPI 3.0 には当分対応しないとのことなので検討を除外しています。

*2:DRF 3.10 以降はデフォルトで OpenAPI ベースの「rest_framework.schemas.openapi.AutoSchema」を使う設定になっているので、追加設定は特に必要ありません。

*3:https://www.django-rest-framework.org/coreapi/

*4:依存パッケージの PyYAML と uritemplate もインストールされます。

Django 組み込みのパスワード再設定(パスワードリセット)の仕組み

$
0
0

この投稿は 「Calendar for Django | Advent Calendar 2021 - Qiita」 1日目の記事です。


akiyoko です。
この記事では、Django 組み込みで提供されているパスワード再設定(パスワードリセット)の仕組みを深掘りします。 Django のパスワード再設定機能に関しては、利用方法についてはさまざまな記事で紹介されていたりするのですが、その内部仕様について詳しく紹介した記事をあまり見かけたことがなかったので、自分のメモがてら残しておこうと思い、ほぼ一年ぶりに筆を執りました。何かのお役に立てば幸いです。




検証環境
  • Django 3.2


 

はじめに

Django は Webアプリケーションを作成するために必要な機能が何でも揃っているフルスタックフレームワークで、ユーザー認証まわりの機能(ユーザーモデル、パーミッション、ユーザーセッションなど)などの便利な機能がデフォルトで用意されています。そしてあまりよく知られていませんが、パスワード再設定(パスワードリセット)機能(で利用できるビューやフォーム)もユーザー認証まわりの機能の一部として「django.contrib.auth(認証システム)」パッケージに含まれています。

なお、「django.contrib.admin(管理サイト)」パッケージにはパスワード再設定系のテンプレートが用意されているので、管理サイト内でパスワード再設定機能を利用するのは非常に簡単です。


 

管理サイトでのパスワード再設定機能の利用方法

管理サイトではパスワード再設定系の機能はデフォルトでオフになっているため、利用するには次のように URLconf にパスワード再設定用の4つの URLパターンを追加しなければいけません(詳しくは公式ドキュメント *1を参照)。

config/urls.py(URLconf)

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path

urlpatterns = [
    path('admin/password_reset/', auth_views.PasswordResetView.as_view(), name='admin_password_reset'),
    path('admin/password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
    path('admin/', admin.site.urls),
]


上で示した「admin_password_reset」というURLパターンが登録されていれば、管理サイトのログイン画面に、次のように「パスワードまたはユーザー名を忘れましたか?」というリンクが表示されるようになります。


f:id:akiyoko:20211127070253p:plain:w350

この追加設定により、次のような画面遷移ができます。

f:id:akiyoko:20211128090512p:plain


それぞれの挙動は参考情報に示したビューやフォームを読めば分かるのですが、ちょっと理解しずらいのが「パスワード再設定メール」の仕組みです。


パスワード再設定メールの仕組み


f:id:akiyoko:20211128100012p:plain:w450

パスワード再設定メール送信画面で「パスワードをリセット」ボタンを押下すると、django.contrib.auth.views.PasswordResetView がリクエストを受け取り、入力したメールアドレスに紐付くアクティブ(is_active が True)なユーザーが存在する場合にのみパスワード再設定用のメールが送信されます。

例えばホストが「127.0.0.1:8000」の場合のパスワード再設定メールは次のようになります。

Subject: 127.0.0.1:8000 のパスワードリセット
From: webmaster@localhost
To: admin@example.com
Date: Fri, 26 Nov 2021 21:47:26 -0000
Message-ID: 
 <163796324660.443.3589748126282107509@1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa>
このメールは 127.0.0.1:8000 で、あなたのアカウントのパスワードリセットが要求されたため、送信されました。
次のページで新しいパスワードを選んでください:
http://127.0.0.1:8000/reset/MQ/awrev2-3a07a0471392cfbc3b885b713b2846d5/
あなたのユーザー名 (もし忘れていたら): admin
ご利用ありがとうございました!
 127.0.0.1:8000 チーム

メール本文にはパスワード再設定画面に遷移するためのリンクが含まれており、リンクの有効期限はデフォルトでは3日間となっています。*2


このリンクの URL は、「password_reset_confirm」という URL パターンで URLconf に登録されている「reset/<uidb64>/<token>/」に相当します。<uidb64>の部分、すなわち上のメールの例の「MQ」は、Base64 でエンコードされたユーザーの PK で、Base64 でデコードすると「1」になります。

<token>の「-」の左側の部分(上の例では「awrev2」)は、メール送信時のタイムスタンプ(2001/1/1 00:00:00 からの経過秒数)を Base34 でエンコードしたもので、リンクの有効期限をチェックするために使われます。残りの右側の部分(上の例では「3a07a0471392cfbc3b885b713b2846d5」)は改ざん防止のためのハッシュです。*3


django.contrib.auth.views.PasswordResetConfirmView では、パスワード再設定URL のリクエストを受け取ると、URL の妥当性(ユーザーが存在するかどうか、URL が改ざんされていないかどうか、有効期限を過ぎていないか)が検証され、「/reset/<uidb64>/set-password/」にリダイレクトされてパスワード再設定画面が表示されます。


f:id:akiyoko:20211128111830p:plain:w450

ちなみに、URL の<token>は(ハッシュ化された)パスワード文字列などから生成されているため(下記参照)、パスワード再設定によってパスワードが変更された後は同じ URL は利用できなくなります。

django.contrib.auth.tokens.PasswordResetTokenGenerator._make_hash_value

def_make_hash_value(self, user, timestamp):
        """        Hash the user's primary key, email (if available), and some user state        that's sure to change after a password reset to produce a token that is        invalidated when it's used:        1. The password field will change upon a password reset (even if the           same password is chosen, due to password salting).        2. The last_login field will usually be updated very shortly after           a password reset.        Failing those things, settings.PASSWORD_RESET_TIMEOUT eventually        invalidates the token.        Running this data through salted_hmac() prevents password cracking        attempts using the reset token, provided the secret isn't compromised."""# Truncate microseconds so that tokens are consistent even if the# database doesn't support microseconds.
        login_timestamp = ''if user.last_login isNoneelse user.last_login.replace(microsecond=0, tzinfo=None)
        email_field = user.get_email_field_name()
        email = getattr(user, email_field, '') or''return f'{user.pk}{user.password}{login_timestamp}{timestamp}{email}'


f:id:akiyoko:20211128111923p:plain:w450

 

まとめ

Django はパスワード再設定(パスワードリセット)機能を組み込みで提供しており、管理サイト内でパスワード再設定機能を利用するのは非常に簡単です。管理サイト外でパスワード再設定を利用する場合は、「django.contrib.auth(認証システム)」パッケージのビューやフォームを使用し、「django.contrib.admin(管理サイト)」パッケージのテンプレートを参考にすればよいでしょう。

パスワード再設定系のビューやフォーム、テンプレートについては次の参考情報をご確認ください。


 

参考情報

パスワード再設定系のビュー・フォーム

  • django.contrib.auth.views.PasswordResetView
  • django.contrib.auth.views.PasswordResetDoneView
  • django.contrib.auth.views.PasswordResetConfirmView
  • django.contrib.auth.views.PasswordResetCompleteView
  • django.contrib.auth.forms.PasswordResetForm
  • django.contrib.auth.forms.SetPasswordForm

パスワード再設定系のテンプレート

  • django/contrib/admin/templates/registration/password_reset_form.html
  • django/contrib/admin/templates/registration/password_reset_done.html
  • django/contrib/admin/templates/registration/password_reset_confirm.html
  • django/contrib/admin/templates/registration/password_reset_complete.html

(メールタイトル・本文のテンプレート)

  • django/contrib/auth/templates/registration/password_reset_subject.txt
  • django/contrib/admin/templates/registration/password_reset_email.html


 

宣伝

Django の技術同人誌をこれまでに4冊出しました。開発のお供にどうぞ。



現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文192ページ。最新の Django 3.2 LTS に対応。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文220ページ。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django 管理サイトのつくり方

Django の管理サイト(Django Admin)だけに特化した、ニッチでオンリーワンな一冊。管理サイトをカスタマイズする前に絶対に読んでほしい本です。B5・本文152ページ。


★ Amazon(電子版)


★ BOOTH(紙の本)

*1:https://docs.djangoproject.com/ja/3.2/ref/contrib/admin/#adding-a-password-reset-feature

*2:有効期限をデフォルトの3日間から変更したい場合は、「PASSWORD_RESET_TIMEOUT」を設定ファイルに定義することで変更可能です。なお、Django 2.2 以前で利用されていた「PASSWORD_RESET_TIMEOUT_DAYS」は非推奨になっており、Django 4.0 で削除される予定です。https://docs.djangoproject.com/en/3.2/internals/deprecation/#deprecation-removed-in-4-0

*3:ハッシュ化アルゴリズムには Django 3.1 以降でデフォルトになった「sha256」が使われています。

Django REST Framework はじめの一歩 〜押さえておきたい3つのポイント〜

$
0
0

この投稿は 「Calendar for Django | Advent Calendar 2022 - Qiita」 1日目の記事です。


この記事では、11月に「みんなのPython勉強会#87」で発表したトーク 「Django REST Framework はじめの一歩 〜押さえておきたい3つのポイント〜」の内容を詳しく説明したいと思います。トークの目的は「Django REST Framework(通称 DRF)がどんなものかをざっくり理解する」です。 DRF はよく分からないけど、Django なら何となく分かるという DRF 初心者の方を理想のトーク対象者として想定しています(もちろん Django を知らなくてもウェルカム!)。

ちなみに、各種バージョンは

  • Django : 3.2 LTS(現時点での最新 LTS)
  • DRF : 3.14(現時点での最新バージョン)

で動作確認をしています。



 

はじめに



今回のトークに先立って事前に Twitter でアンケートを取ったところ、約73%の方が「DRF を使っている」と回答していたのですが、私の予想を遥かに超えて DRF が使われていることに驚きました(正直半々くらいかなと思っていました…)。

ここで、アンケートにご協力いただいた皆さまにこの場を借りて感謝申し上げたいと思います。ご協力ありがとうございました。





また、「Django Developers Community Survey 2021」という7000名を超える開発者アンケートでは、「好きな Django パッケージは何ですか?(複数回答ありで5つまで回答可)」という質問に対して、DRF が第1位に選出されていました。2位の「django-celery」と比較してもダブルスコアというぶっちぎりの結果になっていますね。



 

DRF とは?



本題に入る前にまず、DRF とは何か?を説明したいと思います。一言でいえば、DRF は「REST API を作成する際の多彩なニーズに応えるための超ド定番 Django パッケージ」です。 Django 単体では不足しているさまざまな機能を、DRF で補完してくれているというイメージになります。特に、Django 開発者なら理解しやすい作りになっているというのが、Django 開発者にとってうれしい特徴です。






続いて、REST API の概要についても説明します。
REST API はスマホや SPA のバックエンドとして よく利用されています(左図)。そして、REST API は、URI でリソースを特定し、HTTP メソッドでそのリソースへの操作を表すという直感的に理解しやすいシンプルな設計になっています(右図)。そんな共通の枠組みに則ったデータを介すことで、フロントエンドとバックエンドが疎結合化、すなわち、お互いのプログラミング言語やアーキテクチャを意識することなくデータをやり取りすることができるのです。なお、REST API はリソース中心の設計となっているので、モデル中心の Django と親和性が高いというのも DRF を使うメリットになるかと思います。






DRF が一番得意なのは、単一リソースの CRUD を処理する REST APIです。上の表に示したように、単一リソースの CRUD を処理する REST API は、「GET」「POST」「PUT」「PATCH」「DELETE」という HTTPメソッドそれぞれに意味を持たせ、リソースを表す URI と組み合わせて、「リソース一覧の取得」「リソース詳細の取得」「リソースの登録」「リソースの更新」「リソースの一部更新」「リソースの削除」といった API を構成します。これを DRF で実装するのはすごく簡単で、モデル除けば最短20行くらいで書くことができます(コード例については後ほど紹介します)。






ここで、DRF についてよくある「誤解」を紹介します。
よくある誤解その①「DRF では単一リソースを扱う API 以外は作れない」は「✕」です。先に説明した 単一リソースを扱う REST API 以外にも、上の表に示したような、リソースがネストした API、独自アクションを追加した API、リソースに紐付いていない API なども DRF で作成することができます。





よくある誤解その②「DRF なしでは JSON のやり取りができない」も「✕」です。Django 単体でも(やろうと思えば)JSON データを受け取って JSON データを返すことができます。下の例を見てください。

api/views.py

import json
from django.forms.models import model_to_dict
from django.http.response import JsonResponse
from django.views import View
from shop.models import Book


classBookCreateView(View):
    """本モデルの登録APIクラス"""defpost(self, request, *args, **kwargs):
        """本モデルの登録APIに対応するハンドラメソッド"""
        data = json.loads(request.body)
        # バリデーションを実行ifnot data.get('title'):
            return JsonResponse({'message': 'タイトルは必須です。'}, status=400)
        # モデルオブジェクトを登録
        book = Book(**data)
        book.save()
        # レスポンスオブジェクトを作成して返すreturn JsonResponse(model_to_dict(book), status=201)

入力パラメータとなる JSON データは request.body に入っているのでそれをパースして、(この例ではめちゃくちゃ適当ですが)バリデーションを適宜実行して、モデルオブジェクトにデータを詰め替えて永続化して、最後に JsonResponse オブジェクトを作成して返しています。でもこんなことを毎回するのは大変ですよね。できれば Django っぽい書き方で、こういった処理を共通化したいというニーズから DRF が誕生したんじゃないかなと思います。






次は、DRF を使うメリットについて。
まず、Django のノウハウやエコシステムが活用できるというのが、DRF を使う大きなメリットになります。むしろこれがほとんどすべてと言っても過言ではありません。DRF プロジェクトのベースはあくまでも Django なので、ORM やマイグレーションを始めとする多くの機能をフル活用できたり、いつも使っている Django パッケージが使えたりといったように、Django のノウハウやエコシステムをほぼそのまま使うことができます。

そして次に、人気があって枯れていることもメリットです。DRF は Django で REST API を作るとなった場合にほぼ一択になるほど人気で、特に英語圏での情報がたくさんあります。公式ドキュメントやチュートリアルも(英語になりますが)かなり充実しています。

機能が充実していることも DRF ならではのメリットです。例えば、Cookie認証、トークン認証、JWT認証などの API でよく利用される認証を簡単に利用することができます。また、アクセス権制御をするための「パーミッション」機能、クエリ文字列による検索をするための「フィルタリング」機能、「ページネーション」機能、APIの利用回数制限をするための「スロットリング」機能などが用意されており、それぞれが差し替えできるようになっています。さらに、画面で API を実行確認できる「Browsable API」と呼ばれるテストクライアントがすぐに使えたり、OpenAPI に準拠した API ドキュメントを出力したりすることもできます。こういった REST API を作成するのに必要な機能がたいてい揃っていて、簡単な設定をするだけで幅広いニーズを満たすことができるというのが大きな特徴になります。






逆に、DRF にはどんなデメリットがあるのでしょうか。

ズバリ… 複雑です。

Django だけでも十分複雑なのに、そこに DRF が加わったらさらに複雑になってしまいますよね。






そこでここからは、DRF 初心者が押さえておきたい次の3つのポイントを解説していきたいと思います。

  • ポイント①:全体像を把握しよう
  • ポイント②:シリアライザはフォームっぽく使える
  • ポイント③:起点は DRF 用ビュー

この3つについてそれぞれ詳しく説明していきます。



 

ポイント①:全体像を把握しよう

ポイント①「全体像を把握しよう」について。



まず比較のために、Django プロジェクトの全体像と登場人物について説明します。図の左側から来たリクエストを、URLディスパッチャと呼ばれる Django のコアコンポーネントがハンドリングし、URLconf で登録したビュー関数を呼び出して、ビューの中でフォームやモデル、テンプレートを使って、レスポンスを作成して返し、URLディスパッチャがレスポンスを処理するという流れになっています。ミドルウェアは、ビューが呼び出される前や後で何らかの処理をするために利用されます。






REST API を構築するための DRF プロジェクトでは、(HTML のフォーム要素を扱わないし、レスポンスとして画面をレンダリングした HTML を返さないので)基本的にフォームとテンプレートは使いません(後述しますが、Django を共存させる場合にはその限りではありません)。その代わり、DRF では「シリアライザ」というコンポーネントを使います。シリアライザは入出力の JSON(およびモデルとの連携)定義とバリデーションを担当します。ポイント②で詳しく説明しますが、シリアライザはフォームに似せて作られていて、フォームと同じように使うことができます。

そしてビューは、DRF用のビューを使います。これもポイント③で説明しますが、図を見ると分かるように、DRF の起点となるのは「DRF 用ビュー」です。図では「✕」で示していますが、通常の Django のビューを URLconf に登録しておくことも可能です。

それ以外のモデル、URLconf、ミドルウェアは通常の Django と同じように使います。






ポイント①のまとめです。DRF では、DRF用のビュー を作成して URLconf に登録します。そしてシリアライザというコンポーネントを使い、フォームとテンプレートは使いません。ただし、Django と DRF のビューをそれぞれ登録することができるので、そういった共存をさせる場合にはフォームもテンプレートも使うことになるので誤解のないようにしてください。それ以外は通常の Django とだいたい同じように使います。こういった全体像を押さえておくのが第一のポイントです。




 

ポイント②:シリアライザはフォームっぽく使える

ポイントその②「シリアライザはフォームっぽく使える」について。



その前にまず、DRF の独自コンポーネントである「シリアライザ」の使いどころを説明します。シリアライザが担当する役目は、

  • 入力データのバリデーション
  • モデルオブジェクトの永続化
  • 出力データの作成

です。上の図はどこでシリアライザを使うかを示しているのですが、登録API、更新API、取得API のそれぞれでシリアライザの使いどころが異なります(削除API のようにシリアライザを使わない場合もあります)。例えば登録 API だと、リクエストされた JSON データを元にしてシリアライザオブジェクトを作成し、シリアライザオブジェクトのバリデーションメソッドを実行し、シリアライザの永続化メソッドを実行して、最後にレスポンスオブジェクトの引数にシリアライザのデータを渡すという流れになります。このように、ほとんどの場合でシリアライザを使うことになるため、DRF を使うときにはシリアライザは避けて通ることができません。






さて、本題です。シリアライザはフォームっぽく使えるということについて説明します。リソース(すなわちモデル)と紐付いた REST API の場合は「ModelSerializer」を使うのが基本なのですが、ModelSerializer の使い方は(フォームで特定のモデルを扱うための)ModelForm とほぼ同じです。 ModelSerializer と ModelForm の引数や属性、バリデーション、永続化の流れを上の図に並べて示したのですが、仕組みがほぼ同じになっています。バリデーションメソッドは「is_valid」、永続化メソッドは「save」でこちらも同じです。ただし、バリデーション済みのデータは「validated_data」となっていて、ModelForm(親クラスの Form も同様)と変数名が異なるので注意してください。






シリアライザの実装イメージは次のようになります。

api/serializers.py

from rest_framework import serializers

from shop.models import Book


classBookSerializer(serializers.ModelSerializer):
    """本モデル用シリアライザ"""classMeta:
        model = Book
        fields = ['id', 'title', 'price']

ModelSerializer を継承したシリアライザクラスを用意して、Metaクラスの「model」属性に連携させるモデルクラス、「fields」属性に JSONの入出力フィールドとして利用するモデルのフィールド名を指定します。フィールドは、fields 属性以外に、除外するフィールドを excludes 属性で指定することもできます。

使い方は ModelFormとほぼ同じで、引数「data」にリクエストのパラメータ(DRF では JSON データ)、更新時には引数「instance」にモデルオブジェクトを指定します。バリデーションをおこなたいときは is_valid()、モデルオブジェクトを永続化したいときは save() メソッドを実行します。

>>> from api.serializers import BookSerializer
>>> serializer = BookSerializer(data={'title': 'DRFの本', 'price': 1000})
>>> serializer.is_valid()
True
>>> serializer.validated_data
OrderedDict([('title', 'DRFの本'), ('price', 1000)])
>>> book = serializer.save()

>>> serializer = BookSerializer(instance=book, data={'price': 2000}, partial=True)
>>> serializer.is_valid()
True
>>> serializer.save()
<Book: DRFの本>




ポイント②のまとめです。モデルと紐付いた REST API の場合は ModelSerializer を使うのが簡単で、その使い方は ModelForm とほぼ同じです。




 

ポイント③:起点は DRF 用ビュー

最後のポイント「起点は DRF 用ビュー」について。



DRF には「rest_framework.views.APIView」というクラスが用意されているのですが、「APIView」は DRF の起点となる基底ビュークラスで、これを継承することで、さまざまな前処理と後処理をやってくれます。

1. リクエストオブジェクトを DRF 用に変換
2. レンダラクラスとメディアタイプを決定
3. バージョニング
4. 認証チェック
5. パーミッションチェック
6. スロットリング
7. 例外ハンドリング

これらの処理は差し替え可能です。2. 〜 6. には代表的な処理クラスがいくつか用意されていて、デフォルトの設定を設定ファイル(settings.py)の「REST_FRAMEWORK」で上書きして全体的な設定をしたり、クラス変数をオーバーライドしてビュークラス単位での個別設定をしたりすることができます。






DRF 用のビューは大きく分けて3種類あり、用途に応じて

1)rest_framework.views.APIView
2)rest_framework.generics.CreateAPIView などの 汎用 APIView
3)rest_framework.viewsets.ModelViewSet 系ビュー

のいずれかを継承して作成します。どれを選択するかによってそれぞれカスタム性やコード量が異なってきますが、この後で具体的な実装を見ながら説明していきます(コードがたくさん出てきますが、雰囲気を理解してもらえれば大丈夫です)。

ちなみに下の図で示すように、2) と 3) のビューは「APIView」の子クラスで、APIView は「基本汎用ビュー」と呼ばれる Django の「View」クラスの子クラスです。







まず、1)の APIView を継承したビューの書き方について説明します。

例えば、更新 API で呼び出される post メソッドでは、モデルオブジェクトと入力データからシリアライザオブジェクトを作成し、is_valid() で入力データのバリデーションをおこない、save() でモデルオブジェクトを更新して、最後にシリアライザの data 属性の値を使ってレスポンスオブジェクトを作成して返しています。

api/views.py

from rest_framework import status, views
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response

from shop.models import Book
from .serializers import BookSerializer


classBookRetrieveUpdateAPIView(views.APIView):
    """本モデルの取得(詳細)・更新APIクラス"""defget(self, request, pk, *args, **kwargs):
        """本モデルの取得(詳細)APIに対応するハンドラメソッド"""# モデルオブジェクトを取得
        book = get_object_or_404(Book, pk=pk)
        # シリアライザオブジェクトを作成
        serializer = BookSerializer(instance=book)
        return Response(serializer.data, status.HTTP_200_OK)

    defput(self, request, pk, *args, **kwargs):
        """本モデルの更新APIに対応するハンドラメソッド"""# モデルオブジェクトを取得
        book = get_object_or_404(Book, pk=pk)
        # シリアライザオブジェクトを作成
        serializer = BookSerializer(instance=book, data=request.data)
        # バリデーションを実行
        serializer.is_valid(raise_exception=True)
        # モデルオブジェクトを更新
        serializer.save()
        # レスポンスオブジェクトを作成して返すreturn Response(serializer.data, status.HTTP_200_OK)

コードは長くなりますが、好きなように書くことができます。しかし、単一リソースの CRUD 処理をする場合、モデルとシリアライザだけが違うビューを追加する場合に同じようなコードを何度も書くのはさすがに冗長ですよね。






そんな場合に利用したいのが、2)の「汎用 APIView」です。上の表に示したように、対応アクションごとにいろいろな汎用 APIView が用意されているので、用途に合わせてどれかを継承して、モデルオブジェクトを取得するためのクエリを指定するための「queryset」とシリアライザクラスを指定するための「serializer_class」をクラス変数を加えるだけです。

これでだいぶ短く書くことができますが、単一モデルのCRUD処理をするというのを逸脱した API を作ろうとすると、親クラスのメソッドをオーバーライドして拡張する必要があります。


api/views.py

from rest_framework import generics

from shop.models import Book
from .serializers import BookSerializer


classBookListCreateAPIView(generics.ListCreateAPIView):
    """本モデルの取得(一覧)・登録APIクラス"""

    queryset = Book.objects.all()
    serializer_class = BookSerializer


classBookRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    """本モデルの取得(詳細)・更新・一部更新・削除APIクラス"""

    queryset = Book.objects.all()
    serializer_class = BookSerializer

config/urls.py

from django.urls import path
from api import views

urlpatterns = [
    path('api/books/', views.BookListCreateAPIView.as_view()),
    path('api/books/<pk>/', views.BookRetrieveUpdateDestroyAPIView.as_view()),
]




最後に、3)ModelViewSet 系ビューを継承したビューの書き方です。冒頭で「単一リソースの CRUD を処理する REST API なら超簡単」で「20行くらいで API が全部書ける」と言っていたのはこちらです。

上の表に書いたように ModelViewSet は「一覧」「詳細」「登録」「更新」「一部更新」「削除」という6つの API を全部網羅しています。実装は、下に示したように ModelViewSet を継承して、「serializer_class」と「queryset」の指定をするだけです。

api/views.py

from rest_framework import viewsets

from shop.models import Book
from .serializers import BookSerializer


classBookViewSet(viewsets.ModelViewSet):
    """本モデルのCRUD用APIクラス"""

    serializer_class = BookSerializer
    queryset = Book.objects.all()


あとは、ViewSet 用の SimpleRouter(または DefaultRouter)を利用して URL パターンを URLconf に追加してあげれば、これで API の実装は完了です。シリアライザを含めて最短20行ほどで書くことができます。

config/urls.py

from django.urls import include, path
from rest_framework import routers
from api import views

router = routers.DefaultRouter()
router.register('books', api.BookViewSet)

urlpatterns = [
    # すべてのアクション(一覧・詳細・登録・更新・一部更新・削除)をまとめて追加
    path('api/', include(router.urls)),
]

なので、単一モデルの CRUD を処理する REST API をまるっと実装したいというニーズには「ModelViewSet」が一番最速で実装できるということになります。






ポイント③のまとめです。単一モデルの CRUD を処理する REST API をまるっと実装したい場合は ModelViewSet 系ビューを使うのが一番簡単で、複雑なことがしたい場合は APIView(または GenericAPIView)を継承したビュークラスを使うのがよいでしょう。



 

まとめ

最後のまとめです。



DRFを使う場合、Django に DRF の要素、コンポーネントが加わることになるので複雑になります。なのでまずは、全体像を把握しましょう。一見複雑にみえますが、新しい要素は「シリアライザ」と「DRF用ビュー」です。

「シリアライザ」は入力データのバリデーション、内部に保持したモデルオブジェクトの永続化、出力データの作成を担当するクラスで、Django のフォームと同じような使い方ができます。

「DRF用ビュー」は大きく3種類あり、まるっと API 全部を実装できてしまうものもあれば、複雑なカスタム API にも対応できるものもあるので、用途に合わせて親クラスと書き方を使い分けてください。


さてこれで、DRF がどんなものか完全に理解できたでしょうか。もっと DRF のことを深く知りたいという方は、「現場で使える Django REST Framework の教科書」という 220ページ以上まるまる DRF について書かれた素敵な本があるので、そちらを読んでみてください。ここで説明した内容をもっとディープに解説しています。そして何と、今月この本が改訂予定です。Django 3.2 LTS、DRF 3.14 対応で、Vue 3(+ Vite)と Vuetify 3 でチュートリアルを刷新しています。




トークのスライドはこちらにアップしています。

speakerdeck.com




 

宣伝

Django の技術同人誌をこれまでに4冊出しました。開発のお供にどうぞ。



現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文192ページ。最新の Django 3.2 LTS に対応。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文220ページ。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django 管理サイトのつくり方

Django の管理サイト(Django Admin)だけに特化した、ニッチでオンリーワンな一冊。管理サイトをカスタマイズする前に絶対に読んでほしい本です。B5・本文152ページ。


★ Amazon(電子版)


★ BOOTH(紙の本)


既存テーブルに単一の主キーがない場合に Django モデルを使いたい場合の解決案(Django で複合主キーを使う方法)

$
0
0

この投稿は 「Calendar for Django | Advent Calendar 2022 - Qiita」 2日目の記事です。

最近こんなの見つけたよという緩い内容になっているので、「こんなのもあるんだな」という軽い気持ちで読んでいただければと思います。そしてもし、「現場で使ってるよ」という方がいれば教えていただければありがたいです。

課題

まず大前提ですが、Django モデルの仕様として、それぞれのモデルごとに単一の主キーを持たせる必要があり(「primary=True」オプションを持つフィールドをモデルに明示的に含めるか、さもなくば「id」という名前のフィールドが自動的にモデルに追加され、それに対応したカラムがテーブルに作成される)、複合主キー(composite primary key)はサポートされていません。*1


そのため、例えば、単一の主キーを持たない(が複合主キーを持っている)既存のテーブルを Django モデルで扱うことは困難です。



ところで Django で複合主キーっぽいことをしたければ通常は、「id」という名前のサロゲートキーを主キーとして別に用意しつつ、複合ユニーク制約を利用しますよね。具体的には、Django 2.2 から追加された UniqueConstraintを利用して次のように実装します。


models.py

from django.db import models


classEmployee(models.Model):
    """従業員モデル"""classMeta:
        db_table = 'employee'
        verbose_name = verbose_name_plural = '従業員'
        constraints = [
            models.UniqueConstraint(fields=['branch_code', 'employee_code'], name='unique_employee')
        ]

    branch_code = models.CharField('支店コード', max_length=3)
    employee_code = models.CharField('従業員コード', max_length=5)
    name = models.CharField('従業員名', max_length=255)

    def__str__(self):
        return f'{self.branch_code}-{self.employee_code}'


この従業員テーブルの仕様としては、支店ごとに従業員コードが振られていて、従業員コードはレコード全体としてはユニークになっていない(支店コードと従業員コードを合わせるとユニークになる)という想定です。

ちなみに複合ユニーク制約を実現するには unique_togetherを使うことも可能ですが、将来的に非推奨になる可能性があり、UniqueConstraint の方が多機能なので、UniqueConstraint の利用が推奨されます。



 

解決策

最近見つけたのですが、(次期リリースではありますが)「Viewflow」というライブラリの「CompositeKey」を使えば、既存テーブルに主キーがないテーブルを Django モデルで扱うことが可能になります。

使い方は次の通りです。

pip install django-viewflow --pre

# or

pip install django-viewflow==2.0.0a2


models.py

from django.db import models
from viewflow.fields import CompositeKey


classEmployee(models.Model):
    """従業員モデル"""classMeta:
        db_table = 'employee'
        managed = False
        verbose_name = verbose_name_plural = '従業員'id = CompositeKey(columns=['branch_code', 'employee_code'])
    branch_code = models.CharField('支店コード', max_length=3)
    employee_code = models.CharField('従業員コード', max_length=5)
    name = models.CharField('従業員名', max_length=255)

    def__str__(self):
        return f'{self.branch_code}-{self.employee_code}'


これで、CompositeKey の columns で指定した複数のフィールドの値を「{'branch_code': '001', 'employee_code': '00001'}」のような JSON 文字列で保持する「id」という名前の 仮想フィールドを持つことができます。

「managed = False」*2を指定しないとマイグレーションで「id」カラムが作成されてしまうのですが、CompositeKey が威力を発揮するのは「managed = False」を指定してモデルをマイグレーションの対象外にした場合で、主キーを持たない既存のテーブルに影響を及ぼさずに Django モデルを用意することができます。事例としては、次のように複合主キーを持った既存テーブルから Django ORM を使ってレコードを抽出(読み取り専用)したいというニーズに応えることができます。


Django 管理サイトでも動作するというのも高評価です。

*1:Django で複合主キーをサポートするかどうかは、17年前から議論が続いています… #373 (Add support for multiple-column primary keys) – Django

*2:https://docs.djangoproject.com/ja/3.2/ref/models/options/#managed

Django のビューから Django コマンドを実行する方法

$
0
0

この投稿は 「Calendar for Django | Advent Calendar 2022 - Qiita」 3日目の記事です。

小ネタです。


Django のビューから直接 Djangoコマンドを実行するには、django.core.management.call_command を使えば簡単です。*1


こんな感じで使います。

api/views.py

from django.core import management
from rest_framework.response import Response
from rest_framework.views import APIView


classCallCommandView(APIView):
    defpost(self, request, *args, **kwargs):
        # Djangoコマンドを実行
        management.call_command('loaddata', 'test_data', verbosity=0)
        # レスポンスオブジェクトを作成して返すreturn Response({'success': True})


これは Django REST Framework(通称 DRF)を使用したビューの実装例ですが、Django 単体でも同じように利用することが可能です。



config/urls.py

from django.urls import path

from api import views

urlpatterns = [
    path('call-command/', views.CallCommandView.as_view()),
]


実行結果





ちなみに、Django コマンドの「--no-input」オプションは call_command() に「interactive=False」を指定することで利用可能です。

management.call_command('flush', verbosity=0, interactive=False)


標準出力をファイルに書き込むこともできます。*2

withopen('/path/to/command_output', 'w') as f:
    management.call_command('dumpdata', stdout=f)


cron などで定期実行している Django コマンドを、API で呼び出すなどといった利用方法が考えられますね。

*1:ビューからでなくても、モデルでもどこでも実行可能です。

*2:https://docs.djangoproject.com/ja/3.2/ref/django-admin/#output-redirection

Django パッケージ利用実態調査アンケート(2022年12月)について

$
0
0

この投稿は 「Calendar for Django | Advent Calendar 2022 - Qiita」 4日目の記事です。


Django Advent Calendar のネタ探しをしていて、ふと 「どんな Djangoパッケージがどれくらい使われているの?🤔」という利用実態調査みたいな記事なら自分もぜひ読んでみたいと思ったので、アンケートを採ってみることにしました。


ということで、ぜひアンケートにご協力よろしくお願いします 🙇‍♂️🙇‍♂️🙇‍♂️(回答者の情報は収集していません)

docs.google.com



アンケートの回答〆切は 12/15 23:59 までとさせていただき、12/16 以降の Django Advent Calendar のどこかで発表する予定です。


なお、Q2. の Django パッケージの選択肢を以下に列挙しますが、これは 2022 Django Developers Surveyというワールドワイドな Django 開発者向けアンケートの「お気に入りのサードパーティ Django パッケージは?」という質問の選択肢をそのまま利用しました(なぜこの選択肢になったのかは私にも分かりません)。


Django パッケージ説明文
dj-database-urlDjango を使用したアプリケーションで、データベースの接続文字列を簡単に扱うためのライブラリです。このライブラリを使うことで、データベースの接続情報を環境変数や設定ファイルから読み取り、Django の設定に渡すことができます。その結果、Django アプリケーションのデプロイやテストを容易に行うことができるようになります。また、dj-database-url は、様々なデータベース管理システムをサポートしているため、どのようなデータベースを使用しているかに関係なく利用できます。
dj-rest-knoxDjango の REST フレームワークである Django REST framework にて、トークンベースの認証を提供するためのものです。このパッケージを使用することで、Django アプリケーションでトークンベースの認証を実装することができます。また、dj-rest-knox は Django REST framework の上に構築されており、Django のログイン認証システムを使用している場合は、すぐに使用することができます。
dj-rest-authDjango の REST フレームワークである Django REST framework にて、ユーザー認証を提供するためのものです。このパッケージを使用することで、Django アプリケーションで簡単にユーザー認証を実装することができます。また、dj-rest-auth は Django のログイン認証システムを拡張しているため、既存の Django アプリケーションでも簡単に導入することができます。
django-allauthDjango にて、多要素認証を提供するためのものです。このパッケージを使用することで、Django アプリケーションで簡単に多要素認証を実装することができます。また、django-allauth は Django のログイン認証システムを拡張しており、既存の Django アプリケーションでも簡単に導入することができます。また、django-allauth は様々な外部サービス(Facebook、Google、Twitter など)との連携もサポートしています。
django-bracesDjango にて、様々な拡張機能を提供するためのものです。このパッケージを使用することで、Django アプリケーションで簡単にビューやミックスインなどの拡張機能を実装することができます。また、django-braces は他の Django パッケージとも組み合わせて使用することができ、Django プロジェクトで様々な拡張機能を組み合わせることができます。
django-celeryWeb フレームワークである Django と、Python のタスク駆動フレームワークである Celery を組み合わせるためのものです。このパッケージを使用することで、Django アプリケーション内で Celery を利用して、非同期タスクを管理することができます。
django-celery-beatDjango フレームワークと Celery というタスクキューライブラリを組み合わせて使用するためのツールを提供します。Celery は、バックグラウンドで処理を行うタスクを管理するためのライブラリです。django-celery-beat を使うことで、Django プロジェクトで Celery のスケジュール処理をより簡単に利用することができます。また、django-celery-beat には、Django のデータベースを使って Celery のスケジュールを管理するためのツールが提供されているため、Django プロジェクトで定期的なタスクを実行するためのインフラストラクチャを構築することができます。
django-channelsDjango にて、WebSocket や Server-Sent Events(SSE)を扱うためのものです。このパッケージを使用することで、Django アプリケーションで WebSocket や SSE を利用した通信を実装することができます。
django-clickPython の Webフレームワークである Django を使用したアプリケーションで、コマンドラインインターフェース(CLI)を簡単に構築するためのライブラリです。その結果、Django アプリケーションの管理や操作を、より便利でスムーズに行うことができるようになります。
django-compressorDjango にて、JavaScript や CSS のファイルを圧縮するためのものです。このパッケージを使用することで、Django アプリケーションで簡単に JavaScript や CSS のファイルを圧縮することができます。
django-configurationsDjango にて、複数の環境に対応した設定を管理するためのものです。このパッケージを使用することで、Django アプリケーションで簡単に複数の環境(開発環境、本番環境、ステージング環境など)に対応した設定を管理することができます。
django-cors-headersDjango にて、クロスオリジンリソースシェアリング(CORS)を扱うためのものです。CORS は、異なるオリジン(ドメイン)間での Web ページのやり取りを許可する仕組みです。
django-crispyformsこのパッケージを使用することで、Django アプリケーションで簡単にフォームを美しくレンダリングすることができます。また、django-crispy-forms は Bootstrap を使用したフォームのレンダリングをサポートしています。
django-dbbackupこのパッケージを使用することで、Django アプリケーションで簡単にデータベースのバックアップを管理することができます。また、django-dbbackup は様々なクラウドストレージサービス(Amazon S3、Dropbox、Google Cloud Storage など)との連携もサポートしています。
django-debug-toolbarDjango にて、開発時にデバッグを行うためのものです。このパッケージを使用することで、Django アプリケーションの開発時に、リクエストやレスポンスなどの詳細情報を表示することができます。
django-environDjango アプリケーションで環境変数を簡単に使用できるようにするためのライブラリです。このライブラリを使用することで、アプリケーションの設定値を環境変数から取得することができます。これにより、アプリケーションの設定値を外部から安全かつ簡単に管理することができます。
django-extensionsDjango フレームワークのためのサポートライブラリです。このライブラリには、Django アプリケーションを開発するためのさまざまな便利な機能が含まれています。例えば、コマンドラインから Django プロジェクトを作成するためのツール、データベースのスキーマを表示するためのツール、データベースのデータをダンプするためのツールなどがあります。このライブラリを使用することで、Django アプリケーションの開発プロセスをよりスムーズかつ効率的に行うことができます。
django-filterDjango アプリケーションでクエリセットをフィルタリングするためのライブラリです。このライブラリを使用することで、クエリセットを簡単に様々な条件でフィルタリングし、特定の条件にマッチするデータのみを取得することができます。 django-filter を使用することで、データベースからデータを抽出する際に、より細かいフィルタリングを行うことができるため、データをより正確かつ効率的に取得することができます。
django-import-exportDjangoフレームワークでのデータのインポートおよびエクスポートを簡単に行うためのライブラリです。このライブラリを使用することで、Djangoのモデルデータを様々な形式のファイル(CSV、JSON、Excelなど)とやりとりすることができます。django-import-export を使用することで、データのインポートおよびエクスポート操作を管理画面から直接実行することができます。
django-lifecyclePython の Web フレームワークである Django を使用したアプリケーションで、モデルの作成や更新時に実行される処理を定義するためのライブラリです。このライブラリを使うことで、Django のモデルを作成、更新する際に、特定の処理を自動的に実行することができます。例えば、モデルが作成されるときに、特定のメールを送信する処理や、モデルが更新されるときに、特定のログを記録する処理などを定義することができます。これにより、Django アプリケーションの動作をより自動化し、処理の追加や変更を容易に行うことができるようになります。
django-model-utilsDjango フレームワークでモデルを定義する際に便利なユーティリティクラスを提供するライブラリです。このライブラリを使用することで、モデルの作成やカスタマイズがより簡単かつ効率的に行えます。 django-model-utils には、複数のフィールドを1つのフィールドとして扱う「CompositeField」、モデルのフィールド値を暗号化する「EncryptedField」、モデルのフィールド値を自動的に生成する「AutoSlugField」など、様々な便利なユーティリティクラスが含まれています。
django-moneyDjangoフレームワークで組み込みの「Money」フィールドを提供するライブラリです。このライブラリを使用することで、データベース内での金額を貨幣単位を考慮した形で保存することができます。django-moneyを使用することで、金額を扱うアプリケーションを作成する際に、正確かつ柔軟な金額処理が行えるようになります。また、django-moneyは、様々な通貨をサポートしており、通貨の相互変換も可能です。
django-redisDjango フレームワークで Redis を使用するためのライブラリです。Redis は、速度が非常に速いキー/値型のデータベースです。django-redis を使用することで、Django アプリケーションで Redis を使用してデータを保存することができます。これにより、Djangoアプリケーションのデータ保存や取得が高速化されます。また、django-redis は、Django のキャッシュフレームワークと組み合わせて使用することで、アプリケーションのパフォーマンスをさらに向上させることができます。
django-rest-swaggerDjango フレームワークで作成された RESTful API に Swagger を組み込むためのライブラリです。Swagger は、RESTful API を文書化して公開するためのツールです。django-rest-swagger を使用することで、Django アプリケーションで作成した RESTful API を Swagger を使用して文書化することができます。これにより、API を使用する開発者が API の仕様や使用方法を簡単に理解することができるようになります。また、django-rest-swagger を使用することで、API のテストやモックを作成することができます。
django-storagesDjango フレームワークでさまざまなクラウドストレージサービスを使用するためのライブラリです。このライブラリを使用することで、Django アプリケーションで Amazon S3 や Google Cloud Storage などのクラウドストレージサービスを簡単に使用することができます。 django-storages を使用することで、Django アプリケーションで保存されるデータや画像などのファイルをクラウドストレージ上に保存することができます。これにより、アプリケーションのデータを安全かつスケーラブルな方法で保存することができるようになります。
django-silkDjango フレームワークでリクエストとレスポンスをモニタリングするためのライブラリです。このライブラリを使用することで、Djangoアプリケーションで発生するHTTPリクエストとレスポンスを管理画面から簡単に閲覧することができます。django-silkを使用することで、アプリケーションのパフォーマンスやエラーを把握することができるため、デバッグやトラブルシューティングがより容易に行えます。また、django-silkは、リクエストとレスポンスの中身を表示するだけでなく、リクエストを再現したり、リクエストやレスポンスを検索したりすることができます。
django-taggitDjango フレームワークでタグを扱うためのライブラリです。このライブラリを使用することで、Djangoアプリケーションでタグを作成したり、タグを付けられたオブジェクトを取得したりすることができます。django-taggit を使用することで、アプリケーションにタグ付け機能を簡単に実装することができます。また、django-taggit は、既存の Django モデルにタグを追加することもできます。これにより、タグを使用してデータを管理したり、検索したりすることができます。
django-test-plusDjango フレームワークでのユニットテストを実行するためのライブラリです。このライブラリを使用することで、Django アプリケーションのユニットテストをより簡単かつ効率的に行うことができます。 django-test-plus には、ユニットテストを実行する際に便利なユーティリティ関数やアサーションが含まれています。例えば、ビューやURLのテストを行うためのヘルパー関数、フォームやモデルのテストを行うためのアサーションなどがあります。これらの機能を使用することで、Djangoアプリケーションのユニットテストをよりスムーズかつ効率的に実行することができます。
djangorestframeworkDjango フレームワークで RESTful な Web API を作成するためのライブラリです。このライブラリを使用することで、Django アプリケーションを通じて RESTful な Web API を提供することができます。 djangorestframework には、API のエンドポイントやリクエストとレスポンスのシリアライザ、API の認証や権限管理など、RESTful な Web API を実装するための必要な機能がすべて提供されています。このライブラリを使用することで、Django アプリケーションから RESTful な Web API を提供することができるようになります。
djangorestframework-simplejwtdjango-rest-framework を使用した RESTful な Web API にJSON Web Tokens(JWT)を使用した認証を実装するためのライブラリです。JWT は、トークンベースの認証方式の一種です。djangorestframework-simplejwt を使用することで、django-rest-framework で作成した RESTful な Web API に JWT を使用した認証を実装することができます。これにより、API を利用するアプリケーションがAPIに対してアクセスする際に、トークンを使用して認証を行うことができるようになります。
DjoserDjango フレームワークで作成された RESTful な Web API にユーザー管理機能を実装するためのライブラリです。このライブラリを使用することで、Djangoアプリケーションにユーザー登録、ユーザー情報の管理、パスワードの変更などの機能を簡単に実装することができます。また、Djoser は、JSON Web Tokens(JWT)を使用した認証をサポートしているため、JWT を使用した API の認証も実装することができます。Djoser を使用することで、Django アプリケーションにRESTful な API を作成し、ユーザー管理や認証を実装することができるようになります。
model-bakeryDjango プロジェクトで使用されるモデルを生成するための Python パッケージです。model-bakery は、モデルを生成し、操作し、評価するためのツールを提供します。
pylint-djangopylint-django は、Django フレームワークで作成された Python コードを検証するための Pylint プラグインです。Pylint は、Python のコード品質を測定するツールです。pylint-django を使用することで、Django フレームワークを使用した Python コードに対して、Pylint が提供する様々な品質指標を適用することができます。これにより、Django フレームワークを使用した Python コードの品質を向上させることができます。また、pylint-django は、Django フレームワーク固有の構文や API を理解しており、Django フレームワークを使用した Python コードをより正確かつ効率的に検証することができます。
pytest-djangoPython のテストフレームワークである pytest を用いて Django Web アプリケーションのテストを実行するためのプラグインです。このプラグインを使うことで、Django アプリケーションのテストを簡単に実行し、結果を比較することができます。また、pytest-django は、Django アプリケーションのデータベースを自動的にセットアップ・クリーンアップするため、データベースのテストも容易に行うことができます。
WagtailWagtail は、Django フレームワークを使用した CMS(Content Management System)です。このCMSを使用することで、WebサイトやWebアプリケーションのコンテンツの管理をより簡単かつスムーズに行うことができます。 Wagtail には、ページやブログ記事の管理、メディアライブラリ、ページのテンプレートやカスタマイズ、検索、ユーザー管理などの機能が備わっています。これらの機能を使用することで、WebサイトやWebアプリケーションのコンテンツを効率的に管理することができます。また、Wagtail はオープンソースであるため、カスタマイズや拡張も容易に行うことができます。


ちなみに、説明文は最近話題の「ChatGPT」の回答を使わせていただきました(なのでしれっと間違っている可能性もあるかもしれません)。冗長な説明があったのを削除して調整しましたが、スゴイですねこれ。

現場ではどんな Django パッケージがどれくらい使われているのか(Django パッケージ利用実態調査:2022年12月)

$
0
0

この投稿は 「Django Advent Calendar 2022」 23日目の記事です。


ふと、「実際の現場ではどんな Django パッケージがどれくらい使われているの?🤔」という疑問が湧いてきたので、自分で次のようなアンケートを採ってみることにしました。


docs.google.com



本記事では、この利用実態調査アンケートの結果発表に加えて、ちょっとした分析をしてみたいと思います。なお、本アンケートには40人の方から回答をいただくことができました。皆さま、ご協力ありがとうございました。





 

Q1.回答者の Django 経験年数

Q1.Django の経験はどのくらいですか?

  • 経験なし
  • 1年未満
  • 1〜3年くらい
  • 3〜5年くらい
  • 5年以上


回答者の Django 経験年数の分布は次のようになりました。



一番多かったグループは「1〜3年くらい」で 40%でした。なお、「1年未満」の階級値を「0.5」、「1〜3年くらい」を「2」、「3〜5年くらい」を「4」、「5年以上」を「7.5」として計算した 回答者の Django 経験年数の平均は 3.8 年でした。



 

Q2.Django パッケージ利用状況

代表的な Django パッケージ 35個について、それぞれの利用状況を「聞いたことがない」「聞いたことはあるが使ったことはない」「試しに使ったことがある」「現場で使っている(過去に使っていた)」の4択でアンケートしました。

パッケージの選定にあたっては、毎年数千名が参加する Django 開発者向けアンケート「2022 Django Developers Survey」の「お気に入りのサードパーティ Django パッケージは?」という質問の選択肢をそのまま利用しました。*1

Q2.次の Django パッケージをそれぞれどのくらい利用していますか?

  • 1: 聞いたことがない
  • 2: 聞いたことはあるが実際に使ったことはない
  • 3: 試しに使ったことがある
  • 4: 現場で使っている(過去に使っていた)

Django パッケージの選択肢


回答結果を 4 > 3 > 2 > 1 の優先順に並べ替えた利用度ランキングがこちらになります。






3人に2人が現場で使っているという「djangorestframework」(DRF)がダントツの1位でした。「試しに使ったことがある」を足し合わせると95%というのも衝撃の結果ですよね。「django-cors-headers」の利用度も高いことから、現場で DRF を使って API 開発をしている方が多いことが伺えます。

次点の 「django-debug-toolbar」は2人に1人くらいが現場で使っているという結果になりました。私は開発時にはどのプロジェクトにも「django-debug-toolbar」と「django-extensions」はとりあえず入れるようにしていますが、そんな感じで「とりあえず生!🍺」みたいに使われているのかもしれません。*2


「4: 現場で使っている」「3: 試しに使ったことがある」「2: 聞いたことはある」を足し合わせたものを「認知度」とすると、認知度が高いほど、現場で使っている割合が全体的に高い傾向にありますが、認知度の割には現場で利用されていない(現場で使っていない割には認知度が高いとも解釈できる?)パッケージとして「django-allauth」「django-crispyforms」「django-channels」などが挙げられるでしょうか。



なお、「django-celery」は開発が長年停滞していて現時点で公式サポートされているバージョンの Django には対応していないようなのですが、今回のアンケートでは過去に現場で使っていたということで票が入ったのでしょう。*3




 

経験年数による変化

ここで Q1. とQ2. の結果を組み合わせて、少し考察をしてみたいと思います。

まず、経験年数のグループごとに「現場で使っている」と回答したパッケージ数の平均を比較してみます。




3〜5年くらいを境にして、利用しているパッケージの数が跳ね上がっているように見えます。だいたい3年目くらいから複数の現場を経験するようになるということなのかもしれませんね。


次に、「Django 経験が長い人ほど利用度が高くなるパッケージはどれか?」を調べるために、経験年数とパッケージ利用度の相関を計算してみました。相関が高い Django パッケージランキングの上位5つを下に示します。


#Django パッケージ相関係数
1django-cors-headers0.64
2django-silk0.60
3django-extensions0.57
4django-channels0.55
5django-filter0.54


これらはベテランならではのニーズに答えてくれるパッケージということなのでしょうか。まだこれらのパッケージを試したことがないという方は、ぜひチェックしてみてください。



 

PyPI ダウンロード数のランキングとの比較

The 10 Most-Used Django Packages | LearnDjango.com」という記事にインスパイアされ、PyPI のダウンロード数を元にした別角度での利用度ランキングを作成してみました。PyPI のダウンロード数は「Top PyPI Packages」というサービスから取得しました。




パッケージ名の右側のカッコ内の数字は先に示した「現場で使っている」ランキングの順位ですが、多少のブレはあるものの今回の結果と比較して違和感のない結果になっている印象です。

直近のダウンロード数を元にしていることで、プロジェクトがすでにアーカイブ済みになっている「django-rest-swagger」のダウンロード数が少なかったり、開発が長年停滞している「django-celery」などのパッケージがランキング外になっていたりするのは、まさに現時点での「利用実態」を表していると言えるでしょう。


 

GitHub スター数のランキングとの比較

次に、12/14 時点の GitHub スター数で並べた人気ランキングを見てみます。




同じく、カッコ内の数字は先に示した「現場で使っている」ランキングの順位ですが、(PyPI のダウンロード数ランキングと比べて)少し乖離が大きいような感じがします。

例えば、上位にランクインしている「Wagtail」は8年以上、「django-allauth」は12年以上も継続しているリポジトリということでスター数が多くなっている可能性がありますし、「django-channels」は Django の公式コミュニティが開発しているということで注目度が高いためにスター数が多くなっている可能性があります。また、現在のスター数は多いけれども実際はもう使われなくなってしまったリポジトリもあったりするので、実際の利用実態と乖離していることもあるでしょう。


ということで、パッケージを選定する際には、GitHub スター数を「人気度」として参考にしつつ、PyPI の直近のダウンロード数を「利用度」として判断材料にするのがよいのではないでしょうか。なお、実際の選定にあたっては次の点も留意するのがよいでしょう。

  • 開発が停滞していないか(直近の PyPI ダウンロード数である程度把握できる)
  • Issue が放置されていないか
  • 公式ドキュメントが充実していること
  • コントリビュータが数人以上いること(ぼっち開発は危険)



 

まとめ

独自のアンケートを採って、「実際の現場ではどんな Django パッケージがどれくらい使われているのか?」を調査しました。

「Q2.Django パッケージ利用状況」の結果のグラフを見ると、特異的に認知度が高いものがいくつかあったものの、全体的に、認知度が高いほど現場で利用されている度合いが高い傾向にあることが分かりました。結果としては、「djangorestframework」が認知度・利用度ともダントツのトップでした。

今回は、私が全然知らなかった Django パッケージも含めてアンケートをすることができたので、大変興味深く調査することができました。アンケートにご協力いただいた皆さま、誠にありがとうございました。改めて御礼申し上げます。



参考までに、アンケート結果の可視化に利用したコードは ↓ にアップしています。

github.com




 

(おまけ)ChatGPT による Django パッケージの説明

今回のアンケートの選択肢として挙げたそれぞれの Django パッケージの説明文を載せておきます。ちなみにこの説明文は、最近話題の「ChatGPT」の回答を使わせていただきました。ざっとチェックしましたが、しれっと大嘘をついている可能性があるかもしれませんのでご注意ください。

Django パッケージ説明文
djangorestframeworkDjango フレームワークで RESTful な Web API を作成するためのライブラリです。このライブラリを使用することで、Django アプリケーションを通じて RESTful な Web API を提供することができます。 djangorestframework には、API のエンドポイントやリクエストとレスポンスのシリアライザ、API の認証や権限管理など、RESTful な Web API を実装するための必要な機能がすべて提供されています。このライブラリを使用することで、Django アプリケーションから RESTful な Web API を提供することができるようになります。
django-debug-toolbarDjango にて、開発時にデバッグを行うためのものです。このパッケージを使用することで、Django アプリケーションの開発時に、リクエストやレスポンスなどの詳細情報を表示することができます。
django-filterDjango アプリケーションでクエリセットをフィルタリングするためのライブラリです。このライブラリを使用することで、クエリセットを簡単に様々な条件でフィルタリングし、特定の条件にマッチするデータのみを取得することができます。 django-filter を使用することで、データベースからデータを抽出する際に、より細かいフィルタリングを行うことができるため、データをより正確かつ効率的に取得することができます。
django-storagesDjango フレームワークでさまざまなクラウドストレージサービスを使用するためのライブラリです。このライブラリを使用することで、Django アプリケーションで Amazon S3 や Google Cloud Storage などのクラウドストレージサービスを簡単に使用することができます。 django-storages を使用することで、Django アプリケーションで保存されるデータや画像などのファイルをクラウドストレージ上に保存することができます。これにより、アプリケーションのデータを安全かつスケーラブルな方法で保存することができるようになります。
django-cors-headersDjango にて、クロスオリジンリソースシェアリング(CORS)を扱うためのものです。CORS は、異なるオリジン(ドメイン)間での Web ページのやり取りを許可する仕組みです。
django-extensionsDjango フレームワークのためのサポートライブラリです。このライブラリには、Django アプリケーションを開発するためのさまざまな便利な機能が含まれています。例えば、コマンドラインから Django プロジェクトを作成するためのツール、データベースのスキーマを表示するためのツール、データベースのデータをダンプするためのツールなどがあります。このライブラリを使用することで、Django アプリケーションの開発プロセスをよりスムーズかつ効率的に行うことができます。
django-environDjango アプリケーションで環境変数を簡単に使用できるようにするためのライブラリです。このライブラリを使用することで、アプリケーションの設定値を環境変数から取得することができます。これにより、アプリケーションの設定値を外部から安全かつ簡単に管理することができます。
pytest-djangoPython のテストフレームワークである pytest を用いて Django Web アプリケーションのテストを実行するためのプラグインです。このプラグインを使うことで、Django アプリケーションのテストを簡単に実行し、結果を比較することができます。また、pytest-django は、Django アプリケーションのデータベースを自動的にセットアップ・クリーンアップするため、データベースのテストも容易に行うことができます。
django-import-exportDjangoフレームワークでのデータのインポートおよびエクスポートを簡単に行うためのライブラリです。このライブラリを使用することで、Djangoのモデルデータを様々な形式のファイル(CSV、JSON、Excelなど)とやりとりすることができます。django-import-export を使用することで、データのインポートおよびエクスポート操作を管理画面から直接実行することができます。
django-allauthDjango にて、多要素認証を提供するためのものです。このパッケージを使用することで、Django アプリケーションで簡単に多要素認証を実装することができます。また、django-allauth は Django のログイン認証システムを拡張しており、既存の Django アプリケーションでも簡単に導入することができます。また、django-allauth は様々な外部サービス(Facebook、Google、Twitter など)との連携もサポートしています。
djangorestframework-simplejwtdjango-rest-framework を使用した RESTful な Web API にJSON Web Tokens(JWT)を使用した認証を実装するためのライブラリです。JWT は、トークンベースの認証方式の一種です。djangorestframework-simplejwt を使用することで、django-rest-framework で作成した RESTful な Web API に JWT を使用した認証を実装することができます。これにより、API を利用するアプリケーションがAPIに対してアクセスする際に、トークンを使用して認証を行うことができるようになります。
django-redisDjango フレームワークで Redis を使用するためのライブラリです。Redis は、速度が非常に速いキー/値型のデータベースです。django-redis を使用することで、Django アプリケーションで Redis を使用してデータを保存することができます。これにより、Djangoアプリケーションのデータ保存や取得が高速化されます。また、django-redis は、Django のキャッシュフレームワークと組み合わせて使用することで、アプリケーションのパフォーマンスをさらに向上させることができます。
django-celeryWeb フレームワークである Django と、Python のタスク駆動フレームワークである Celery を組み合わせるためのものです。このパッケージを使用することで、Django アプリケーション内で Celery を利用して、非同期タスクを管理することができます。
dj-database-urlDjango を使用したアプリケーションで、データベースの接続文字列を簡単に扱うためのライブラリです。このライブラリを使うことで、データベースの接続情報を環境変数や設定ファイルから読み取り、Django の設定に渡すことができます。その結果、Django アプリケーションのデプロイやテストを容易に行うことができるようになります。また、dj-database-url は、様々なデータベース管理システムをサポートしているため、どのようなデータベースを使用しているかに関係なく利用できます。
django-silkDjango フレームワークでリクエストとレスポンスをモニタリングするためのライブラリです。このライブラリを使用することで、Djangoアプリケーションで発生するHTTPリクエストとレスポンスを管理画面から簡単に閲覧することができます。django-silkを使用することで、アプリケーションのパフォーマンスやエラーを把握することができるため、デバッグやトラブルシューティングがより容易に行えます。また、django-silkは、リクエストとレスポンスの中身を表示するだけでなく、リクエストを再現したり、リクエストやレスポンスを検索したりすることができます。
django-model-utilsDjango フレームワークでモデルを定義する際に便利なユーティリティクラスを提供するライブラリです。このライブラリを使用することで、モデルの作成やカスタマイズがより簡単かつ効率的に行えます。
django-crispyformsこのパッケージを使用することで、Django アプリケーションで簡単にフォームを美しくレンダリングすることができます。また、django-crispy-forms は Bootstrap を使用したフォームのレンダリングをサポートしています。
django-rest-swaggerDjango フレームワークで作成された RESTful API に Swagger を組み込むためのライブラリです。Swagger は、RESTful API を文書化して公開するためのツールです。django-rest-swagger を使用することで、Django アプリケーションで作成した RESTful API を Swagger を使用して文書化することができます。これにより、API を使用する開発者が API の仕様や使用方法を簡単に理解することができるようになります。また、django-rest-swagger を使用することで、API のテストやモックを作成することができます。
dj-rest-authDjango の REST フレームワークである Django REST framework にて、ユーザー認証を提供するためのものです。このパッケージを使用することで、Django アプリケーションで簡単にユーザー認証を実装することができます。また、dj-rest-auth は Django のログイン認証システムを拡張しているため、既存の Django アプリケーションでも簡単に導入することができます。
WagtailWagtail は、Django フレームワークを使用した CMS(Content Management System)です。このCMSを使用することで、WebサイトやWebアプリケーションのコンテンツの管理をより簡単かつスムーズに行うことができます。 Wagtail には、ページやブログ記事の管理、メディアライブラリ、ページのテンプレートやカスタマイズ、検索、ユーザー管理などの機能が備わっています。これらの機能を使用することで、WebサイトやWebアプリケーションのコンテンツを効率的に管理することができます。また、Wagtail はオープンソースであるため、カスタマイズや拡張も容易に行うことができます。
django-compressorDjango にて、JavaScript や CSS のファイルを圧縮するためのものです。このパッケージを使用することで、Django アプリケーションで簡単に JavaScript や CSS のファイルを圧縮することができます。
DjoserDjango フレームワークで作成された RESTful な Web API にユーザー管理機能を実装するためのライブラリです。このライブラリを使用することで、Djangoアプリケーションにユーザー登録、ユーザー情報の管理、パスワードの変更などの機能を簡単に実装することができます。また、Djoser は、JSON Web Tokens(JWT)を使用した認証をサポートしているため、JWT を使用した API の認証も実装することができます。Djoser を使用することで、Django アプリケーションにRESTful な API を作成し、ユーザー管理や認証を実装することができるようになります。
django-channelsDjango にて、WebSocket や Server-Sent Events(SSE)を扱うためのものです。このパッケージを使用することで、Django アプリケーションで WebSocket や SSE を利用した通信を実装することができます。
django-celery-beatDjango フレームワークと Celery というタスクキューライブラリを組み合わせて使用するためのツールを提供します。Celery は、バックグラウンドで処理を行うタスクを管理するためのライブラリです。django-celery-beat を使うことで、Django プロジェクトで Celery のスケジュール処理をより簡単に利用することができます。また、django-celery-beat には、Django のデータベースを使って Celery のスケジュールを管理するためのツールが提供されているため、Django プロジェクトで定期的なタスクを実行するためのインフラストラクチャを構築することができます。
django-taggitDjango フレームワークでタグを扱うためのライブラリです。このライブラリを使用することで、Djangoアプリケーションでタグを作成したり、タグを付けられたオブジェクトを取得したりすることができます。django-taggit を使用することで、アプリケーションにタグ付け機能を簡単に実装することができます。また、django-taggit は、既存の Django モデルにタグを追加することもできます。これにより、タグを使用してデータを管理したり、検索したりすることができます。
model-bakeryDjango プロジェクトで使用されるモデルを生成するための Python パッケージです。model-bakery は、モデルを生成し、操作し、評価するためのツールを提供します。
django-dbbackupこのパッケージを使用することで、Django アプリケーションで簡単にデータベースのバックアップを管理することができます。また、django-dbbackup は様々なクラウドストレージサービス(Amazon S3、Dropbox、Google Cloud Storage など)との連携もサポートしています。
pylint-djangopylint-django は、Django フレームワークで作成された Python コードを検証するための Pylint プラグインです。Pylint は、Python のコード品質を測定するツールです。pylint-django を使用することで、Django フレームワークを使用した Python コードに対して、Pylint が提供する様々な品質指標を適用することができます。これにより、Django フレームワークを使用した Python コードの品質を向上させることができます。また、pylint-django は、Django フレームワーク固有の構文や API を理解しており、Django フレームワークを使用した Python コードをより正確かつ効率的に検証することができます。
django-bracesDjango にて、様々な拡張機能を提供するためのものです。このパッケージを使用することで、Django アプリケーションで簡単にビューやミックスインなどの拡張機能を実装することができます。また、django-braces は他の Django パッケージとも組み合わせて使用することができ、Django プロジェクトで様々な拡張機能を組み合わせることができます。
django-configurationsDjango にて、複数の環境に対応した設定を管理するためのものです。このパッケージを使用することで、Django アプリケーションで簡単に複数の環境(開発環境、本番環境、ステージング環境など)に対応した設定を管理することができます。
django-clickPython の Webフレームワークである Django を使用したアプリケーションで、コマンドラインインターフェース(CLI)を簡単に構築するためのライブラリです。その結果、Django アプリケーションの管理や操作を、より便利でスムーズに行うことができるようになります。
django-test-plusDjango フレームワークでのユニットテストを実行するためのライブラリです。このライブラリを使用することで、Django アプリケーションのユニットテストをより簡単かつ効率的に行うことができます。 django-test-plus には、ユニットテストを実行する際に便利なユーティリティ関数やアサーションが含まれています。例えば、ビューやURLのテストを行うためのヘルパー関数、フォームやモデルのテストを行うためのアサーションなどがあります。これらの機能を使用することで、Djangoアプリケーションのユニットテストをよりスムーズかつ効率的に実行することができます。
django-moneyDjangoフレームワークで組み込みの「Money」フィールドを提供するライブラリです。このライブラリを使用することで、データベース内での金額を貨幣単位を考慮した形で保存することができます。django-money を使用することで、金額を扱うアプリケーションを作成する際に、正確かつ柔軟な金額処理が行えるようになります。また、django-money は、様々な通貨をサポートしており、通貨の相互変換も可能です。
dj-rest-knoxDjango の REST フレームワークである Django REST framework にて、トークンベースの認証を提供するためのものです。このパッケージを使用することで、Django アプリケーションでトークンベースの認証を実装することができます。また、dj-rest-knox は Django REST framework の上に構築されており、Django のログイン認証システムを使用している場合は、すぐに使用することができます。
django-lifecyclePython の Web フレームワークである Django を使用したアプリケーションで、モデルの作成や更新時に実行される処理を定義するためのライブラリです。このライブラリを使うことで、Django のモデルを作成、更新する際に、特定の処理を自動的に実行することができます。例えば、モデルが作成されるときに、特定のメールを送信する処理や、モデルが更新されるときに、特定のログを記録する処理などを定義することができます。これにより、Django アプリケーションの動作をより自動化し、処理の追加や変更を容易に行うことができるようになります。


 

宣伝

Django の技術同人誌をこれまでに4冊出しました。開発のお供にどうぞ。



現場で使える Django の教科書《基礎編》

「現場で使える Django の教科書」シリーズ第1弾となる Django の技術同人誌。Django を現場で使うための基礎知識やベストプラクティスについて、初心者・初級者向けに解説した本です。B5・本文192ページ。最新の Django 3.2 LTS に対応。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django の教科書《実践編》

《基礎編》の続編にあたる「現場で使える Django の教科書」シリーズの第2弾。認証まわり、ファイルアップロード、ユニットテスト、デプロイ、セキュリティ、高速化など、さらに実践的な内容に踏み込んでいます。現場で Django を本格的に活用したい、あるいはすでに活用している方にピッタリの一冊。B5・本文180ページ。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django REST Framework の教科書

Django で REST API を構築する際の鉄板ライブラリである「Django REST Framework」(通称「DRF」)にフォーカスした、「現場で使える Django の教科書」シリーズの第3弾。B5・本文220ページ。


★ Amazon(電子版/ペーパーバック)


★ BOOTH(紙の本)



現場で使える Django 管理サイトのつくり方

Django の管理サイト(Django Admin)だけに特化した、ニッチでオンリーワンな一冊。管理サイトをカスタマイズする前に絶対に読んでほしい本です。B5・本文152ページ。


★ Amazon(電子版)


★ BOOTH(紙の本)

*1:そもそもなぜこの35個が選ばれたのかというのは分かりませんでした。。orz

*2:私は飲めませんが。。

*3:Django 2.2 未対応という致命的な問題があるため、「django-celery-results」や「django-celery-beat」が代替となっているようです。

Django 5.0 で削除された「USE_L10N」とは何だったのか? そして今後はどうなるのか?

$
0
0

この投稿は 「Djangoのカレンダー | Advent Calendar 2023 - Qiita」 15日目の記事です。

小ネタです。


Django 5.0 で「USE_L10N」が削除されました。

https://docs.djangoproject.com/ja/5.0/releases/5.0/#features-removed-in-5-0より



そもそも Django には「USE_I18N 」と「USE_L10N」という設定があり、その変数名からそれぞれが「国際化 (internationalization) 」と「ローカル化 (localization) 」に対応するかと思いきや、Django においては、「USE_I18N 」は言語設定に合わせたテキストの多言語対応、「USE_L10N」は言語設定に合わせた日時や数字のフォーマット対応を扱います。これらの変数の微妙なネーミングには歴史的な経緯があるとのこと。

国際化 (internationalization)
ソフトウェアをローカル化に備えさせることです。通常、開発者によって行われます。
ローカル化 (localization)
翻訳およびローカルな表示形式を記述することです。通常、翻訳者によって行われます。


Translation is controlled by the USE_I18N setting. However, it involves internationalization and localization. The name of the setting is an unfortunate result of Django's history.
(翻訳はUSE_I18N設定によって制御されます。しかし、これは国際化とローカライゼーションに関わります。この設定の名前は Django の歴史が生んだ不幸な結果です。)


https://docs.djangoproject.com/ja/5.0/topics/i18n/より



なお、多言語対応については、芝田さんの「実践Django Pythonによる本格Webアプリケーション開発」に詳しく書かれているのでぜひどうぞ。

あとは、公式ドキュメントの 翻訳 | Django ドキュメント | Djangoあたりをご参照ください。




本題に入ります。

Django 4.2 まで使われている「USE_L10N」は、先に述べた通り、言語設定に合わせた日時や数字の表示形式を扱うための設定です。

もっと具体的に言えば、主にテンプレートに変数を表示する際、変数が数値型・日時型・日付型・時刻型の場合に、「USE_L10N」と「LANGUAGE_CODE」の設定内容にしたがったフォーマット変換をしてくれる設定ということになります。


もちろん、管理サイトのモデル一覧画面などの表示にも適用されます。




Django 4.2 以前は、「USE_L10N」は次のように動作します。

「USE_L10N」が False の場合、設定ファイルの「DATETIME_FORMAT」や「DATE_FORMAT」のフォーマットに変換される(設定が無ければ、global_settings.py のデフォルトのフォーマットに従って変換される)。


「USE_L10N」が True の場合、(設定ファイルや global_settings.py のフォーマット設定に関係なく)「LANGUAGE_CODE」で指定した言語ごとのフォーマットに変換される。


「LANGUAGE_CODE」で指定した言語ごとのフォーマットというのは、「conf/locale/ja/」のようにロケールごとに置かれた「formats.py」に書かれたフォーマットを指します。例えば、日本語の場合は次に示す formats.py がデフォルトで用意されています。

django/conf/locale/ja/formats.py

# This file is distributed under the same license as the Django package.## The *_FORMAT strings use the Django date format syntax,# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
DATE_FORMAT = "Y年n月j日"
TIME_FORMAT = "G:i"
DATETIME_FORMAT = "Y年n月j日G:i"
YEAR_MONTH_FORMAT = "Y年n月"
MONTH_DAY_FORMAT = "n月j日"
SHORT_DATE_FORMAT = "Y/m/d"
SHORT_DATETIME_FORMAT = "Y/m/d G:i"# FIRST_DAY_OF_WEEK =# The *_INPUT_FORMATS strings use the Python strftime format syntax,# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior# DATE_INPUT_FORMATS =# TIME_INPUT_FORMATS =# DATETIME_INPUT_FORMATS =
DECIMAL_SEPARATOR = "."
THOUSAND_SEPARATOR = ","
NUMBER_GROUPING = 3


Django 5.0 以降で「USE_L10N」が削除されますが、言語設定に合わせて日時や数字の表示フォーマットを変更できなくなるわけではありません。「USE_L10N」が常に True 扱いになったと考えればよいだけです。ご安心ください。


Django 5.0 以降で日時や数字の表示フォーマットをデフォルトのものから変更するには、Django 4.2 以前と同じように「FORMAT_MODULE_PATH」*1を使って、言語に合わせた formats.py のフォーマット設定を書き換えてあげればよいです。


config/settings.py

LANGUAGE_CODE = 'ja'

FORMAT_MODULE_PATH = 'conf.locale'

(Django 5.0 以降では「USE_L10N」の設定は不要です。むしろ、設定してもまったく意味がありません。)


conf/locale/ja/formats.py

DATE_FORMAT = 'Y-m-d'
DATETIME_FORMAT = 'Y-m-d H:i'


(ディレクトリ構成例)

.
|-- conf
|   |-- __init__.py
|   `-- locale
|       |-- __init__.py
|       `-- ja
|           |-- __init__.py
|           `-- formats.py
|-- config
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- test.py
|   |-- urls.py
|   |-- views.py
|   `-- wsgi.py
・
・


Viewing all 160 articles
Browse latest View live