@kyanny's blog

事実上すべての広告に見られる一貫したテーマとは、消費者の劣等性である - W・B・キイ「メディア・レイプ」

Django: FileField が admin でリンクになる理由

django.db.models.FileField と django admin を使って作ったファイルアップロードフォームを使うと、ファイル名がアップロードしたファイルへのリンクになる*1

↓NAME 列の fruits.csv がリンクになっている

https://user-images.githubusercontent.com/10515/104941840-ee505100-59f6-11eb-93cc-51c2d4f8a243.png

models.py

from django.db import models

# Create your models here.
class Upload(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.FileField()

admin.py

from django.contrib import admin
from .models import Upload

# Register your models here.
class UploadAdmin(admin.ModelAdmin):
    list_display = ('id', 'name',)


admin.site.register(Upload, UploadAdmin)

どういう仕組みで自動的にリンク (a タグ) が作られるか気になったので調べた。

つまり、 FileField がリンクになるのを防ぐことはできない。

(実は、リンクにしない方法が無いか調べていて、そもそもなぜリンクになるんだろう?リンクになる仕組みがわかれば、コントロールする方法も見つかるのでは?と考えて調べた)

サンプルプロジェクトは

https://github.com/kyanny/django-model-filefield

に置いた。


なお、この調査のとっかかりとしては、 rg -g '*.py' 'field-' で django/contrib/admin 以下を雑に検索して https://github.com/django/django/blob/3.1.5/django/contrib/admin/templatetags/admin_list.py#L231 を見つけ、周囲のコードを読んで条件分岐を一つずつ調べていって display_for_field の呼び出しが当たりかな、と見当をつけた。

これに限らず、ある程度大きなライブラリやフレームワークの調査をするときは、(ドキュメントやウェブ検索で解決しない場合は)ログメッセージとか出力する HTML コードの特徴的な部分*2でソースコードを grep して当たりをつける、というやり方を渋々、仕方なくやることが多いが、泥臭くて非効率なやり方だという自覚がある。みんなどういう風に調べているんだろうか。

*1:ただし MEDIA_ROOT や urls.py を適切に設定しないと実際のファイルにアクセスできない。

*2:id, class など

Django: SQL クエリ実行回数を数える

Django Debug Toolbar が出す SQL 実行回数を Django shell や単体のスクリプトからも使いたい。

python - Get SQL query count during a Django shell session - Stack Overflow に叡智が詰まっている。

  • django.db.connection.queries に発行された SQL クエリの数が入っている
  • Django アプリケーションが複数のコネクションを扱う場合は django.db.connections.all() と組み合わせる
  • connection.queries のカウントをリセットするには django.db. reset_queries を使う

https://github.com/kyanny/django-select_related-prefetch_related-Prefetch/tree/master/mysite を例に試すとこういう感じ。

gist.github.com

python manage.py shell
In [1]: with open('query_count.py') as f:
   ...:     exec(f.read())
   ...:

In [2]: from polls.models import Choice, Question

In [3]: for c in Choice.objects.all():
   ...:         c.question
   ...:
(0.002) SELECT "polls_choice"."id", "polls_choice"."question_id", "polls_choice"."choice_text", "polls_choice"."votes" FROM "polls_choice"; args=()
(0.001) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 1 LIMIT 21; args=(1,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 1 LIMIT 21; args=(1,)
(0.001) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 1 LIMIT 21; args=(1,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 1 LIMIT 21; args=(1,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 2 LIMIT 21; args=(2,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 2 LIMIT 21; args=(2,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 2 LIMIT 21; args=(2,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 2 LIMIT 21; args=(2,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 3 LIMIT 21; args=(3,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 3 LIMIT 21; args=(3,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 3 LIMIT 21; args=(3,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 3 LIMIT 21; args=(3,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 4 LIMIT 21; args=(4,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 4 LIMIT 21; args=(4,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 4 LIMIT 21; args=(4,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 4 LIMIT 21; args=(4,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 5 LIMIT 21; args=(5,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 5 LIMIT 21; args=(5,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 5 LIMIT 21; args=(5,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 5 LIMIT 21; args=(5,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 6 LIMIT 21; args=(6,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 6 LIMIT 21; args=(6,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 6 LIMIT 21; args=(6,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 6 LIMIT 21; args=(6,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 7 LIMIT 21; args=(7,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 7 LIMIT 21; args=(7,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 7 LIMIT 21; args=(7,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 7 LIMIT 21; args=(7,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 8 LIMIT 21; args=(8,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 8 LIMIT 21; args=(8,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 8 LIMIT 21; args=(8,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 8 LIMIT 21; args=(8,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 9 LIMIT 21; args=(9,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 9 LIMIT 21; args=(9,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 9 LIMIT 21; args=(9,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 9 LIMIT 21; args=(9,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 10 LIMIT 21; args=(10,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 10 LIMIT 21; args=(10,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 10 LIMIT 21; args=(10,)
(0.000) SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."id" = 10 LIMIT 21; args=(10,)

In [4]: print(query_coun
query_count     query_count.py

In [4]: print(query_count())
41

In [5]: for c in Choice.objects.select_related('question').all():
   ...:         c.question
   ...:
(0.001) SELECT "polls_choice"."id", "polls_choice"."question_id", "polls_choice"."choice_text", "polls_choice"."votes", "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_choice" INNER JOIN "polls_question" ON ("polls_choice"."question_id" = "polls_question"."id"); args=()

In [6]: print(query_count())
1

Nintendo Switch を買った

妻と暮らし始めて15年、ついに据え置き型のゲーム機を購入するに至った。

結婚祝いにWiiをプレゼントされたことや、友人の結婚式二次会のビンゴゲームで3DSを当てたことはあった。それ以外にも、中古のゲームボーイとか携帯ゲーム機を入手したことはあったが、テレビと繋ぐゲーム機を買う機会はこの15年で初めて訪れた。

妻は筋金入りのゲーム・アニメ嫌いで、付き合い始めて最初の一、二年は「少しずつ楽しさに慣れさせていこう」と考えていたものの、頑なにその手のもの全般に拒否反応を示す様子を見るにつれ、「この人と一緒になるということは、もう一生オタクカルチャーとは無縁になるのかな」と諦めかけたこともあった(メジャーなゲームには全くオタク要素はないが)。

とはいえ人は影響を受けて変わるもので、妻も特定のタイトルであればアニメを観たりするようになった(なぜか「しろくまカフェ」を大変気に入って、今も部屋で流れてる)。先日はテレビで放送していた「風の谷のナウシカ」をおれと一緒に最後まで視聴するほどの成長ぶりを見せた。もちろん、過去に何度もジブリの話題になるたびにナウシカを観るといいと勧めるなど、地道な啓蒙活動あってこその成果である。

妻は真面目な性格で、美味しいものを食べたり飲んだりするのに目がないのにコロナ禍で外出自粛ムードになってからは楽しみだった知人との食事も一年近く我慢し続けていて、ストレス解消手段が少なくてかわいそうだった。おれは基本的に引きこもり気質で、一人で無限に暇潰しし続けられるので外出自粛は苦にならないのだが、そのギャップも課題ではあった。

PlayStation や Xbox で流行るようなゲームは基本的に一人プレイ用かネットワーク対戦もので、なんであれ妻は興味を持たないだろうからブランドごと諦めていたが、任天堂ならいけるのではないか?という予感はあった。妻が好んで見ている猫ブロガーとかネット漫画家とかが「どうぶつの森」の話をしていた、などと話を聞いたりするうちにその確信は深まっていき、 Nintendo Switch なら妻も首を縦に振る日が来るのではないかと希望をつないできた。もちろん折に触れてスプラトゥーンをやってみたいとアピールすることも忘れなかった。

年末年始にも何度か、いよいよ新たな暇潰しツールとして Switch あたりを手に入れるべきではないか、という話はしてきた。それ以前からもしてはいたが、 Switch の品薄と転売の話題が主で、「どこもすぐ予約が品切れになる」という感じだった。しかしいずれ在庫は戻ってくるはずで、その時に一押しして在庫があれば買える予感はしていた。

昨日か一昨日、ついに妻が「スイッチ買う?」と切り出してきた。どうやら桃鉄をやりたいらしく、なぜ桃鉄?ぶつ森では?ていうかおれはスプラトゥーンをやってみたいのだが、など思うところはあったが、とにかく在庫があるか見てみようとヨドバシ.comを見るとある。しかし程よく酔って妻の機嫌が良くなるのを待っていたら寝てしまって書いそびれ、今日はヨドバシの在庫が無くなっていた。千載一遇のチャンスを逃したかと思ったが、 Amazon で売っていて「で、結局買うの?買わないの?欲しくないの?」「いやもちろん欲しいよ」、などとやりとりをして、ついに購入した。

本体は一番普通っぽい赤と青のやつで、ソフトはダウンロード版でいいのでセットでは買わず。液晶保護シートは買って、 Amazon の延長保証は買わなかった。

「Switch 買った」だけで1500字も書くのおかしい気がするけど、自分と妻の人生にとってはそれなりに remarkable な出来事だと思ったのでつい長くなった。家庭用ゲーム機の世界で繋がりを持ったりはしたくない(家の中に家族以外の存在を持ち込みたくない)のでネットワーク対戦とか交流の類はせずに家庭内のみでひっそりと楽しむ予定。スプラトゥーンと桃鉄と、妻がやるのであればぶつ森と、あとは是非妻にゼルダをやらせてみたい。

ブラックホーク・ダウン

好きな映画ベスト5に入る「ブラックホーク・ダウン」が Amazon Prime Video でついにダウンロード販売していたので即買った。 590 円と格安だが、値段以上にデータで買えたのが嬉しい。これで DVD を入れ替える手間なくいつでも、スマホでも観られる。

ブラックホーク・ダウン (字幕版)

ブラックホーク・ダウン (字幕版)

  • 発売日: 2013/11/26
  • メディア: Prime Video

数学: ランダムな 6 桁の数字の組み合わせは 100 万通り

SMS で送られてくる二要素認証の認証コードみたいなのをランダムに生成する場合、組み合わせは 106 = 100 万通りある。

irb(main):059:0> 6.times.map{ (0..9).to_a.sample }.join
=> "094575"
irb(main):061:0> 6.times.map{ (0..9).to_a.sample }.join
=> "991260"

これは数学でいうと「順列」の「重複あり」の場合に該当する。

この動画の 2 番目の例題にあたる。

www.youtube.com