Upload
yasushi-masuda
View
1.284
Download
0
Embed Size (px)
DESCRIPTION
This slides introduce a few (slightly maniac) usage of {% with %} template tag and a hack of django-integrated multi DB.
Citation preview
Djangoで黒魔術@whosaysni
自己紹介増田 泰 (@whosaysni)!
http://whosaysni.jp/!!
某バイオベンチャー勤務ですが…!
!一般社団法人PyConJP理事!
PyConJP 9/13-15 CFP中!http://2014.pycon.jp/!
!ろうじん老神.py 幹事!
https://sites.google.com/site/oikamipy/
Django
Django
もうすぐ9歳!
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 スキーママイグレーションが組み込まれる(予定)
おしながき
• {% with %} タグの変態的な誤った使い方
• DjangoのマルチDBをハックする
{% with %}
{% 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.
{% with %} の構文{% with a=123 c='defg' e=hij ... %}
タグ トークン列
トークン トークン トークン
a=123a as 123
古い形式の キーワード引数
キーワード引数
キー 引数
{% 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 までを子ノードにする
{% 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
子ノード(ブロックの内容)
キーワード引数
互換性用のコード
キーワード引数の値を解決して辞書にする
子ノードをレンダリングコンテキストをスタック
コンテキストを復帰
コンテキスト• インタフェースは辞書
• データ構造は辞書のスタック
• 一番上から順に検索する
context['color'] -> 'blue'
context['number'] -> 2
context['food'] -> 'spam'
Context
number=42 food='spam'
animal='duck'
color='blue' number=2
dict
dict
dict
• 要するに、 {% with %} タグは、一時的にコンテキストに辞書を積んでブロックをレンダするタグ
• スコープの外の同名の変数は見えなくなる
• with のキーワード引数は、ブロックのレンダ直前に解決される
ということで・・・
変#1: デフォルト値
• コンテキストに username があるかどうかわからない
• username がなければ「名無し」さん
• username があればその値
{{ username }} これはダメ
{{ username|default:'名無し' }} 一応OK
<span>ようこそ{{ username|default:'名無し' }}さん</span> !... !<h1>{{ username|default:'名無し' }}さんのプロフィール</h1> !... !<h2>{{ username|default:'名無し' }}さんへのおすすめ</h2> !....
(#`Д́)ノノ┻┻;:'、・゙ヤッテラレルカ!
変#1: デフォルト値
{% with username=username|default:'名無し' %} !<span>ようこそ{{ username }}さん</span> !... !<h1>{{ username }}さんのプロフィール</h1> !... !<h2>{{ username }}さんへのおすすめ</h2> !.... !{% endwith %}
• コンテキスト変数を確実に埋めておきたいときは、{% with %}でデフォルト値をオーバライドしたブロックをつくる
• 同じ変数を何度も参照したいときにも、 {% with %} が有効
変#2: {% include %}の悪用
Hello {{ foo }}.
• {% include %} は、レンダ時にテンプレートを展開する(実際には、include 対象をレンダして結果を挿入する)
• レンダ対象にはコンテキストがそのまま渡る
{% with foo='bar' %} !{% include "included.html" %} !{% endwith %}
Hello bar.
変#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
変#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つで、フィールドをカスタマイズできる
Multi-DB
Django Multi-DB
• 1.2で導入された
• バックエンドDBのマスタスレーブ化、シャーディング、レプリケーションを可能にする
• モデル単位でDBのルーティングができる
Django App
Replicate/Partition
RO Access
RW Access
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',...]
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__
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
• と、いうことは、ConnectionHandlerの実装を動的に差し替えてやれば、ランタイムでDBの設定を切り替えられるはず
• DBの設定はPythonオブジェクト→Djangoのモデルにできるはず
やってみましたhttps://gist.github.com/whosaysni/11361218
Thanks!