モデルクラスを継承してメソッドを追加する(proxyを使う場合)

この記事は Django Advent Calendar 2017 の7日目の記事です。

Djangoのモデルクラスを継承した場合、Metaに特に指定が無ければ、マルチテーブル継承となり、migrateを実行すると2つのテーブルができます。

モデルを継承して単一のテーブルにする方法は2つあります。

  • 親のモデルクラスでMetaに abstract = True を指定する
  • 継承した子クラスでMetaに proxy = True を指定する

今回は後者のproxyを指定する方法を紹介します。

https://docs.djangoproject.com/en/2.0/topics/db/models/#proxy-models

この方法は、継承元の親クラスがサードパーティライブラリ等から提供されて、直接書き換えられない場合などに便利です。

継承してproxyを指定したモデル

Djangoに組込みの django.contrib.auth.models.User を継承してみます。

from django.contrib.auth.models import User


class MyUser(User):
    """
    Userモデルを継承するが、新たにテーブルは作られない。
    """
    class Meta:
        proxy = True

    def display(self):
        """追加で定義したメソッド
        """
        print("{full_name}さん".format(full_name=self.get_full_name()))

呼び出し例

>>> from myapp.models import MyUser
>>> MyUser(first_name="Shinya", last_name="Okano").display()
Shinya Okanoさん

ハマりどころ

ForeignKeyなどで参照した場合には、継承した子クラスではなく、ForeignKeyで指定された元のモデルのインスタンスが返されます。

たとえば上記の例だと、 django.contrib.auth.models.Group などから辿った場合は、Userクラスのインスタンスになります。

>>> user = Group.objects.first().user_set.filter(first_name="Shinya").first()
>>> user
<User: >
>>> user.display()  # User.displayはない
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'display'

回避するには、proxyのモデルを起点にQuerySetを作るか、インスタンス__class__ を無理やり書き換えるなどですが、スマートなやり方が現状ないですね。

>>> user.display()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'display'
>>> user.__class__ = MyUser
>>> user
<MyUser: >
>>> user.display()
Shinya Okanoさん

まあ、やっていきましょう。

Djangoフレームワーク組込みのドキュメントジェネレータ(django.contrib.admindocs)の紹介

この記事は Django Advent Calendar 2017 の6日目の記事です。

tell-kさんの記事 を読んでいて、「そういえばDjangoには組込みのドキュメントジェネレータがあるんで紹介しておこう」と思ったので書くことにしました。

タイトルの通り、 django.contrib.admindocs の紹介です。

Djangoフレームワークの黎明期からこの機能は存在してます。ドキュメントもあります。

The Django admin documentation generator | Django documentation | Django

使い方

  1. settings.pyのINSTALLED_APPSに django.contrib.admindocs を追加する。(django.contrib.adminも必要です)
  2. ROOT_URLCONFで指定されているurls.pyに url(r'^admin/doc/', include('django.contrib.admindocs.urls')), を追加する。(このとき url(r'^admin/', admin.site.urls) よりも前に書いておくことに注意する)
  3. 依存モジュールのdocutilsをインストールしておく。
  4. runserverを起動して、Djangoの管理サイトにログインし、右上のメニューに『ドキュメント』リンクがあるのでクリックで移動

動作例

admindocsでは、有効になっているDjangoのタグやフィルター、モデル、ビュー定義が一覧され、docstringなどの説明を参照できます。

f:id:nullpobug:20171206030638p:plain
ドキュメントのトップ

たとえば以下のモデルだとこんな感じ。Django2.0で試しました。

django-example-todo/models.py at master · tokibito/django-example-todo · GitHub

from django.db import models

# Create your models here.
class Todo(models.Model):
    """
    TODOのデータを扱うモデルクラス
    このクラスのインスタンス1つが、データベース上の1レコードに相当します
    """
    name = models.CharField("名称", max_length=50)
    done = models.BooleanField("完了")
    created_at = models.DateTimeField("作成日時", auto_now_add=True)

    def __str__(self):
        return self.name

f:id:nullpobug:20171206025819p:plain
モデルのドキュメント詳細

簡易なものですが、使える場面があるかもしれません。

1年の振り返りなど

この記事はpyspa Advent Calendar 2017の2日目の記事です。

https://adventar.org/calendars/2258

今年1年の振り返りなどいくらか。ほとんど畑の話。

去年畑を借りて、今年も契約を更新しました。

借りている市民農園の畑の使用料は1万円/年です。安い。

契約は最長3年で、3年目の終わりに返却することになるため、残りの期間はあと1年半ほどです。

いくつかの野菜は栽培に失敗しました。

  • 白菜
    • 収穫の時期を逃したせいで大きくなりすぎ、花が咲いた
    • 白菜の花って普通に菜の花やん

f:id:nullpobug:20170422112235j:plain
白菜の花

  • ほうれん草
    • 収穫の時期を逃したせいで大きくなりすぎ、花が咲いた
    • ほうれん草って1m超える大きさになるのか!
  • トマト
    • 植えた時期が遅すぎたか、大きくならず。

気温が変わると一気に育つので気をつけないとなー。

一方、うまくいったものも結構多い。

  • パセリ
    • 畑に植えたものは虫が付きやすく、土がかかって洗うのが面倒だったけど、日当たりがいいとよく育ったのでたくさん収穫できた。
  • たまねぎ
    • 土を盛って埋める必要があるようだった。なかなか大きくならない。

f:id:nullpobug:20170702205946j:plain
パセリとたまねぎともらった大根

f:id:nullpobug:20170709163259j:plain
チーズトースト(自家製のバジルとたまねぎ)

  • えんどうまめ
    • 暖かくなると一気に伸びるので、支柱を建てるのははやめにやったほうがよかった。2mぐらいまでは伸びる
  • きゅうり
    • 去年の種で普通に育った。育つの早すぎなので、苗の本数は3~4本まで減らしてよさそう。
    • 小さめでも取れるうちに取ったほうがよさげ。
  • とうもろこし
    • 虫に這われて食われる
    • 収穫量がそんなに多くないので、本数を増やすのありかも。

f:id:nullpobug:20170717113315j:plain
きゅうりととうもろこし

  • さつまいも
    • 植えてから根づくまでは、乾燥させないように気をつける必要があった。暖かい時期に植えるのだと、最初はかなり多めに水やりしてよさそうだった。
    • 夏場はつるが伸び放題になった。芋がら収穫して食べたけど、処理が面倒
    • マルチで覆って植えたの大正解だったけど、畝の間も覆ってしまったほうがよかったかも。
    • つるが伸びすぎて隣の畑まで出てしまったので、柵を予め用意したほうがよかった
    • 植え付けから110~130日程度で収穫した。かなり大きく育ってよかった。
    • 収穫後、数日乾燥させたほうが包丁が通りやすく、味もよかった。
    • 乾燥させればかなり長持ちするので、おすそ分けしやすい。

f:id:nullpobug:20171028140245j:plain
さつまいも(この量を3回ほど収穫した)

来年は以下を育ててみたい。

  • ナス
  • ピーマン
  • トマト
    • 春すぎには植えて早めに大きくしたほうが良さそう。
  • ローズマリー
    • 種からだと発芽率低いみたいなので、苗を買って育てるのでよさそう。

料理

洋風の調理や、揚げ物、煮物は少し上達したかな。作った野菜の保存処理なんかをもうちょっとうまくしたい。

魚料理の幅を少し広げていきたい。

仕事

プンコレ入社してから1年経ったの早い。ぼちぼちやっています。仕事の幅は広がったかな。

停滞しないよう、引続きやっていきたいと思います。

『Google Homeを使いたおす! DialogflowとAoG Meetup』に参加しました

タイトルの通り、勉強会に参加してきました。

ブログ枠で参加したので、ノルマをこなします。

gcpug-tokyo.connpass.com

感想とか

  • GoogleHomeは持っていて、Dialogflowとつなぐチュートリアルまでは実施済みぐらいの知識で参加した。
    • DialogflowはWebhookのアプリを作ったことあるぐらいの知識
  • AoG、特にDialogflowやGCPを使う事例を知ることができたのは良かった。
  • VoiceUIの考え方やハマりどころなど、新しい分野の情報を得られて良かった。
  • Googleのオフィス!無料自販機!
  • ピザとビール!

当日使われた資料はConnpassのページにリンクがあります。

gcpug-tokyo.connpass.com

以下、聞きながら取ったメモ。

Dialogflowで作るアシスタントアプリ

  • ymotongpooさん
  • Actions on Googleについて
  • VUI(VoiceUserInterface)に期待されるもの
    • そもそも道具を使う目的とは
      • 何かを知りたい、どこかへ行きたい、何かを買いたい
    • 各デバイスのUIの特徴
      • デスクトップアプリはたくさん情報を表示する
      • モバイルアプリは1つの画面を専有する
        • ソフトウェアで必要に応じたUIが提供される
      • ウェアラブルバイスは即時性がとても高いけど情報量が少ない
      • スクリーンデバイスアプリケーション
        • テレビとか
        • とにかく画面が大きい
        • 限られた操作方法で大きな画面でコンテンツを見るのに特化してる
      • ウェアラブルバイス以外は、並列でコンテンツを見せることができる
    • スマートスピーカーはどうか
      • 出力結果は直列でしか取得できない
        • 音声は同時に流せない(流しても聞き取れない)
      • 出力結果を得るのに時間がかかる
        • 最後まで聞かないと結果がわからない
    • VUI使ってもらうにはステップを減らす努力が必要
  • Googleアシスタントについて
    • Googleアシスタントとは
      • Googleと会話して物事の処理をサポートするツール
      • 様々なデバイスから呼び出すことができる
    • GoogleHome
      • Googleアシスタントを搭載したスマートスピーカー
    • Actions on Google
      • Googleアシスタントから呼び出されるアプリケーション
      • 呼び出すデバイスにはサードパーティのアプリケーションをインストールしなくても使える
      • 音声認識音声合成Googleアシスタントが担当してくれる
        • アプリケーションはテキスト処理だけでいい
    • Actions on Googleの呼び出しフローの説明
  • Dialogflow
    • ユーザーの入力とIntentの対応をGUIで設定できる
    • 形態素解析をサービス側でやってくれる
      • 日本語にも対応している
    • ユーザー入力とインテントへの割り振り方を履歴から学習させられる
    • インテントを設定して使う
    • User saysにはなるべくたくさん書いたほうがいい
    • Actionは日本語使わずアスキーにしたほうがいいよ!
    • エンティティをうまくつかってあげると、少ないパターンでいろいろな会話に対応させることができる
    • 質問:マクドナルドの「マック」と、Appleの「マック」でエンティティで別の認識と扱えるか?
      • →コンテキストを切り替えないと無理

Dialogflow + 赤外線で家電管理

  • fishさん
  • Google Homeで(声で)家電を操作したい!
  • 持っているもので実現したい
    • Google home
    • エアコンなど普通の家電
      • 赤外線をなんとかすればできそう
    • IRKit(もらいもの)
      • 赤外線信号の送受信ができる
  • GoogleHome+Dialogflow+バックエンド(Cloudfunctions)+IRKit
  • IRKitはAPIを呼び出して操作できる
  • DialogflowはLINEやFacebookなど、他のサービスにつなげるのが簡単でよかった
    • 今回はActions on Googleに接続
  • CloudFunctionsからIRKitのAPIを呼び出す(HTTP)
  • おまけ
    • エンティティを楽に作れるライブラリ
      • CSVファイルをアップロードしてエンティティを登録できるようにした
      • 近いうちにPyPIに登録します!

RasPi + Assistant SDK + AoGで3Dプリンター管理~Dialogflowで非同期同期やれるかな~

  • soundTrickerさん
  • 会社にいるボットの話
    • 3DプリンタにつながっているRaspiでボットが動いてる
    • 3Dプリンタを監視しているボット
      • 進捗自動通知とか
    • Dialogflowでやっている
      • Slackで動かしているが自然言語処理を使っている
        • 新規の参入者や非エンジニアにはコマンドを憶えるよりも使いやすい
    • CloudFunctionsからイントラ内にあるRaspiのサーバーにpushしている
      • GCPのCloud Pub/Subを使っている
  • SlackボットからAoGへ
    • Pub/Subを使うと非同期になってしまうので、GoogleHomeで扱えない
      • →無理やり同期化する必要がある
    • 無理やり同期化するには
      • DialogflowのWebhook同期処理中で非同期の応答を待つ
      • Webhookは5秒でタイムアウトするので結構失敗する
    • Dialogflowの色とカラーコード
      • 定義済みのEntityとしてsys.colorがあるが、これは追加、削除できないので、使いづらい
        • 金色とかなかった
    • VUIは長い返答は嫌われる
    • VUIは返答の仕方によっては気持ち悪い感じになる
      • 方言なんかは変な発音になったりする
    • アプリケーションを抜けるデザイン
      • 終了方法がわかりづらいので、いろいろな言葉をサポートしておいたほうがよい
  • 時間があったら、自分でGoogleHomeを作る!話をします
    • AIY Projects
    • VoiceKIT
    • Assistant SDK
      • Google Assistantをデバイスに入れるためのSDK
      • 現状、日本語を扱えない

SUUMOとホットペッパーグルメにAoGを使ってローンチしてみた ?デモもあるよ!?

  • 株式会社リクルート住まいカンパニー 嶋田達哉さん

    • SUUMOのボットと会話して物件情報を教えてくれるサービス(Action on Google)
    • WebhookでAWS Lambdaを使っている
      • JPリージョンにCloudFunctionが無くてLambdaのほうが応答速かった
    • 開発で苦労した点
      • Fullfillment webhookに返すJSONフォーマットがドキュメントを見てもわかりづらく、少し違うだけでもエラーになって、原因がわかりづらい
    • Dialogflowのコンテキスト
      • コンテキストで複数パラメータを扱う場合
        • API callしない分、レスポンスが速い
        • 複雑なものはWebhook側で対応したほうがよさそう
  • 株式会社リクルートライフスタイル/株式会社オートマタ 荒川裕紀さん

    • ホットペッパーグルメのボット
      • シーン(日時、場所、人数など)を伝えるとオススメを教えてくれる
      • 金額は選択肢からえらぶ
      • ルーセルで結果を表示
    • デザイン/ペルソナ
    • 気づき
      • 長文はよく拾ってくれる
      • Speech to Textが意図しない言葉を拾ってくる
        • 4人→「余人」
      • 4文字以上の言葉をなるべくユーザーに言ってもらえるようにしたほうがよいかもしれない
      • 対話なので、ユーザーが次に何をしたらいいのかわかるように、文章で説明するのは大切
      • ngrok便利だった

Dialogflow + MAGELLAN BLOCKSで商品のロケーション案内するよ

行き先 確認エージェントを最小手で作る

  • a2cさん
  • GoogleHomeを使って何を作ろうか
    • 行き先確認のアプリを作る
  • IFTTTでAoGから一発入力
    • 慣れると何度もやり取りするのは面倒くさいので一発入力にしたかった
    • IFTTTからSpreadsheetへ書き出し
    • SpreadsheetではAppsScriptで処理
      • 書いたコードは数行、Dialogflowのwebhookの結果のJSONを返すもの
  • つらかった点
    • IFTTTのAPIがなくて画面から全部入力することになった
    • IFTTTにエンティティ相当のものがないので、表現ブレは全部ルールとしてIFTTTに全部入れないといけなかった
  • ヘルプの出し方は工夫した
    • 入力ミスの際にヘルプを返すのがよい
  • 終了の仕方も工夫した
    • 終了の仕方はわかりづらいので、とにかくたくさんキーワードを入れておくのが大切

Hue meetupでDjangoの話をしてきました

Hadoop WebUIソフトウェアのHueのmeetupでDjangoの話をしてきました。

connpass.com

HueはDjangoフレームワークで作られているのですが、どのように使われてるとか、拡張の仕方とかを紹介しました。

スライドは、slideshareにアップロードしています。

スライド中のソースコード、拡張の仕方については、別途記事にまとめようと思います。

PyConJP 2017でDjangoの認証の話をしてきました

9/7~9/10に行われたPyConJP 2017に参加し、『Djangoフレームワークのユーザーモデルと認証』というタイトルで話してきました。

https://pycon.jp/2017/ja/

Djangoフレームワークのユーザーモデルと認証

Djangoの認証まわりを触る前に知っておくとよさそうなことを説明しました。

スライドは、slideshareにアップロードしています。

Django1.10以降のDeferredAttributeとフィールドへのアクセス

Djangoのモデルクラスにフィールドやメソッドを定義しておいて、さらに属性値を代入していて、この値を取得するコードを書く場合。

メソッドのほうはgetattrでとればいいのだけど、同様にDjangoのフィールドのほうはget_fieldでフィールドを取得しないといけない。

メソッドから取得する方法に失敗したら、Djangoのフィールドとみなしてget_fieldメソッドを呼ぶようなコードを書いていた。

しかし、Django1.10以降だとModel.フィールド名でアクセスすると、DeferredAttributeが返され、hasattrがTrueになるため、はまったりしていた。

検証コード

test.py:

def main():
    import django
    django.setup()
    from django.db import models

    class MyModel(models.Model):
        class Meta:
            app_label = "__main__"

        def method1(self):
            pass
        method1.attr2 = "egg"  # この値をとりたい

        field1 = models.CharField(max_length=10)
        field1.attr1 = "spam"  # この値も取りたい

    # メソッドに指定された属性の取得
    print("method: ", MyModel.method1)
    print("hasattr: ", hasattr(MyModel, "method1"))
    print("getattr: ", getattr(MyModel.method1, "attr2", "invalid"))

    # Model.フィールド名だとDeferredAttributeが返されるので同じやり方ではダメ
    print("field: ", MyModel.field1)
    print("hasattr: ", hasattr(MyModel, "field1"))
    print("getattr: ", getattr(MyModel.field1, "attr1", "invalid"))

    # _meta.get_fieldを使うのが正解
    print("attr1: ", MyModel._meta.get_field("field1").attr1)


if __name__ == '__main__':
    main()

実行結果

Django1.9だとDeferredAttributeは返されずエラー(当時はここからフォールバックして_meta.get_fieldでアクセスするコードを書いていた)

$ DJANGO_SETTINGS_MODULE=project.settings venv-dj19/bin/python test.py
method:  <function main.<locals>.MyModel.method1 at 0x7fe61a6b90d0>
hasattr:  True
getattr:  egg
Traceback (most recent call last):
  File "test.py", line 32, in <module>
    main()
  File "test.py", line 23, in main
    print("field: ", MyModel.field1)
AttributeError: type object 'MyModel' has no attribute 'field1'

Django1.10以降はエラーにならなくなった。挙動の違いでハマった。

$ DJANGO_SETTINGS_MODULE=project.settings venv-dj110/bin/python test.py
method:  <function main.<locals>.MyModel.method1 at 0x7f6ac886bea0>
hasattr:  True
getattr:  egg
field:  <django.db.models.query_utils.DeferredAttribute object at 0x7f6ac8de19e8>
hasattr:  True
getattr:  invalid
attr1:  spam

Django1.11

$ DJANGO_SETTINGS_MODULE=project.settings venv-dj111/bin/python test.py
method:  <function main.<locals>.MyModel.method1 at 0x7fd012e3e1e0>
hasattr:  True
getattr:  egg
field:  <django.db.models.query_utils.DeferredAttribute object at 0x7fd01342cbe0>
hasattr:  True
getattr:  invalid
attr1:  spam

参考

Model _meta API | Django documentation | Django