Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Django boodoo

4,057 views

Published on

This slides introduce a few (slightly maniac) usage of {% with %} template tag and a hack of django-integrated multi DB.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Django boodoo

  1. 1. Djangoで黒魔術 @whosaysni
  2. 2. 自己紹介 増田 泰 (@whosaysni)! http://whosaysni.jp/! ! 某バイオベンチャー勤務ですが…! ! 一般社団法人PyConJP理事! PyConJP 9/13-15 CFP中! http://2014.pycon.jp/! ! ろうじん老神.py 幹事! https://sites.google.com/site/oikamipy/
  3. 3. Django
  4. 4. Django
  5. 5. もうすぐ9歳!
  6. 6. 0.95 Djangoドキュメントが和訳されるw 0.98 内部コードがUnicode化される 1.0 メジャーリリース 1.2 マルチDBに対応 1.3 クラスベースビュー、さようならmod_python 1.4 project/app のレイアウトが変更される 1.6 Userを置き換えられるようになった(らしい) 1.7 スキーママイグレーションが組み込まれる(予定)
  7. 7. おしながき • {% with %} タグの変態的な誤った使い方 • DjangoのマルチDBをハックする
  8. 8. {% with %}
  9. 9. {% with %} タグ • テンプレート中で変数を束縛する • ブロック内がスコープ • スコープ内の {% include %} に波及 する • ネスト可 {% with a='foo' b='bar' %} ! Outside nest:{{ a }}/{{ b }}/{{ c }}. ! {% with c=a b='baz' %} ! Inside nest: {{ a }}/{{ b }}/{{ c }}. ! {% endwith %} ! {% endwith %} ! Outside nest: foo/bar/. ! Inside nest: foo/baz/foo.
  10. 10. {% with %} の構文 {% with a=123 c='defg' e=hij ... %} タグ トークン列 トークン トークン トークン a=123 a as 123 古い形式の キーワード引数 キーワード引数 キー 引数
  11. 11. {% with %} の実装(1) @register.tag('with') def do_with(parser, token): ... bits = token.split_contents() remaining_bits = bits[1:] extra_context = token_kwargs(remaining_bits, parser, support_legacy=True) if not extra_context: raise TemplateSyntaxError("%r expected at least one variable " "assignment" % bits[0]) if remaining_bits: raise TemplateSyntaxError("%r received an invalid token: %r" % (bits[0], remaining_bits[0])) nodelist = parser.parse(('endwith',)) parser.delete_first_token() return WithNode(None, None, nodelist, extra_context=extra_context) トークン列を取り出す テンプレートの解析中に with タグを見つけたときの処理 各トークンをキーワード引数 として解析する レンダリングノードを作成する endwith までを子ノードにする
  12. 12. {% with %} の実装(2) class WithNode(Node): def __init__(self, var, name, nodelist, extra_context=None): self.nodelist = nodelist # var and name are legacy attributes, being left in case they are used # by third-party subclasses of this Node. self.extra_context = extra_context or {} if name: self.extra_context[name] = var ! def __repr__(self): return "<WithNode>" ! def render(self, context): values = dict([(key, val.resolve(context)) for key, val in six.iteritems(self.extra_context)]) context.update(values) output = self.nodelist.render(context) context.pop() return output 子ノード(ブロックの内容) キーワード引数 互換性用のコード キーワード引数の値を解決して辞書にする 子ノードをレンダリング コンテキストをスタック コンテキストを復帰
  13. 13. コンテキスト • インタフェースは辞書 • データ構造は辞書のスタック • 一番上から順に検索する context['color'] -> 'blue' context['number'] -> 2 context['food'] -> 'spam' Context number=42 food='spam' animal='duck' color='blue' number=2 dict dict dict
  14. 14. • 要するに、 {% with %} タグは、一時的にコンテ キストに辞書を積んでブロックをレンダするタグ • スコープの外の同名の変数は見えなくなる • with のキーワード引数は、ブロックのレンダ直 前に解決される
  15. 15. ということで・・・
  16. 16. 変#1: デフォルト値 • コンテキストに username が あるかどうかわからない • username がなければ「名無 し」さん • username があればその値 {{ username }} これはダメ {{ username|default:'名無し' }} 一応OK <span>ようこそ{{ username|default:'名無 し' }}さん</span> ! ... ! <h1>{{ username|default:'名無し' }}さん のプロフィール</h1> ! ... ! <h2>{{ username|default:'名無し' }}さん へのおすすめ</h2> ! .... (#`Д́)ノノ┻┻;:'、・゙ヤッテラレルカ!
  17. 17. 変#1: デフォルト値 {% with username=username|default:'名無し' %} ! <span>ようこそ{{ username }}さん</span> ! ... ! <h1>{{ username }}さんのプロフィール</h1> ! ... ! <h2>{{ username }}さんへのおすすめ</h2> ! .... ! {% endwith %} • コンテキスト変数を確実に埋 めておきたいときは、{% with %}でデフォルト値をオーバラ イドしたブロックをつくる • 同じ変数を何度も参照したい ときにも、 {% with %} が有 効
  18. 18. 変#2: {% include %}の悪用 Hello {{ foo }}. • {% include %} は、レンダ時 にテンプレートを展開する(実 際には、include 対象をレン ダして結果を挿入する) • レンダ対象にはコンテキスト がそのまま渡る {% with foo='bar' %} ! {% include "included.html" %} ! {% endwith %} Hello bar.
  19. 19. 変#2: {% include %}の利用 {{ field.label_tag }} <p class="tertiary-text-secondary"> {{ field.help_text }} </p> <div> {% with field_type=type|default:"" %} <div class="input-control" ...> {% ifequal field_type "" %} {{ field }} {% endifequal %} {% ifequal field_type "text" %} {{ field }} <button type="button" class="btn-clear"></button> {% endifequal %} {% ifequal field_type "password" %} {{ field }} <button type="button" class="btn-reveal"></button> {% endifequal %} {% ifequal field_type "switch" %} <label> {{ field }} <span class="check"></span> </label> {% endifequal %} <span class="tertiary-text text-alert"> {{ field.errors.as_text }} </span> </div> {% endwith %} </div> • フォームフィールドをカスタ マイズしたいとき • 正攻法は widget のサブクラ ス化(めんどくさい) • 各フィールドタイプで分岐す るテンプレートを作っておく field.html
  20. 20. 変#2: {% include %}の利用 <div class="container padding10"> <h1>Add user</h1> <div class="padding10"> <form method="POST" action="{% url 'user_add' %}"> {% csrf_token %} <fieldset> {% with field=form.is_superuser type='switch' %}{% include "field.html" %}{% endwith %} {% with field=form.is_active type='switch' %}{% include "field.html" %}{% endwith %} {% with field=form.username type='text' %}{% include "field.html" %}{% endwith %} {% with field=form.password type='password' %}{% include "field.html" %}{% endwith %} {% with field=form.email type='text' %}{% include "field.html" %}{% endwith %} {% with field=form.first_name type='text' %}{% include "field.html" %}{% endwith %} {% with field=form.last_name type='text' %}{% include "field.html" %}{% endwith %} </fieldset> <div class="button-group"> <input type="submit" value="Add" /> </div> </form> </div> </div> テンプレート2つで、フィールドをカスタマイズできる
  21. 21. Multi-DB
  22. 22. Django Multi-DB • 1.2で導入された • バックエンドDBのマスタ スレーブ化、シャーディン グ、レプリケーションを可 能にする • モデル単位でDBのルーティ ングができる Django App Replicate/Partition RO Access RW Access
  23. 23. Multi-DBの構成 • settings に静的に定義 • 名前で区別する • DBのパラメタは辞書 • using() またはデータベー スルータで制御する BE_PG = 'django.db.backends.postgresql_psycopg2' BE_MY = 'django.db.backends.mysql' DATABASES = { 'default': { 'NAME': 'master_db', 'ENGINE': BE_PG, 'USER': 'master_user', 'PASSWORD': 'boo hoo woo' }, 'replicon': { 'NAME': 'replicon_db', 'ENGINE': BE_MY, 'USER': 'replicon_user', 'PASSWORD': 'let it go' } } # 名前でデータベースを指定する User.objects.using('users').get(...) ! # データベースルータを使う class MyDbRouter(object): def db_for_read(self, model, **kw): if ...: return 'replicon' DATABASE_ROUTERS = ['foo.bar.MyDbRouter',...]
  24. 24. Multi-DBの実装 • データベース名は文字列 • django.db.connections を使ってDB接続を取り出 している • connections は辞書ライ クなインタフェースを持つ ConnectionHandlerイン スタンス from django.db import connections ! class QuerySet(object): """ Represents a lazy database lookup for a set of objects. """ def __init__(self, model=None, query=None, using=None): self.model = model self._db = using self.query = query or sql.Query(self.model) ... ! def ... connection = connections[self.db] django.db.models.query from django.db.utils import ConnectionHandler connections = ConnectionHandler() django.db.__init__
  25. 25. Multi-DBの実装 • ConnectionHandlerはDB 設定とコネクションプール をキャッシュしているらし い • __getitem__ で何かゴニョ ゴニョして取り出している 模様 class ConnectionHandler(object): def __init__(self, databases=None): self._databases = databases self._connections = local() ! ... ! def __getitem__(self, alias): if hasattr(self._connections, alias): return getattr(self._connections, alias) ! self.ensure_defaults(alias) db = self.databases[alias] backend = load_backend(db['ENGINE']) conn = backend.DatabaseWrapper(db, alias) setattr(self._connections, alias, conn) return conn django.db.utils
  26. 26. • と、いうことは、ConnectionHandlerの実装を 動的に差し替えてやれば、ランタイムでDBの設 定を切り替えられるはず • DBの設定はPythonオブジェクト→Djangoのモ デルにできるはず
  27. 27. やってみました https://gist.github.com/whosaysni/11361218
  28. 28. Thanks!

×