Django boodoo

28
Djangoで黒魔術 @whosaysni

description

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

Transcript of Django boodoo

Page 1: Django boodoo

Djangoで黒魔術@whosaysni

Page 2: Django boodoo

自己紹介増田 泰 (@whosaysni)!

http://whosaysni.jp/!!

某バイオベンチャー勤務ですが…!

!一般社団法人PyConJP理事!

PyConJP 9/13-15 CFP中!http://2014.pycon.jp/!

!ろうじん老神.py 幹事!

https://sites.google.com/site/oikamipy/

Page 3: Django boodoo

Django

Page 4: Django boodoo

Django

Page 5: Django boodoo

もうすぐ9歳!

Page 6: Django boodoo

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 スキーママイグレーションが組み込まれる(予定)

Page 7: Django boodoo

おしながき

• {% with %} タグの変態的な誤った使い方

• DjangoのマルチDBをハックする

Page 8: Django boodoo

{% with %}

Page 9: Django boodoo

{% 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.

Page 10: Django boodoo

{% with %} の構文{% with a=123 c='defg' e=hij ... %}

タグ トークン列

トークン トークン トークン

a=123a as 123

古い形式の キーワード引数

キーワード引数

キー 引数

Page 11: Django boodoo

{% 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 までを子ノードにする

Page 12: Django boodoo

{% 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

子ノード(ブロックの内容)

キーワード引数

互換性用のコード

キーワード引数の値を解決して辞書にする

子ノードをレンダリングコンテキストをスタック

コンテキストを復帰

Page 13: Django boodoo

コンテキスト• インタフェースは辞書

• データ構造は辞書のスタック

• 一番上から順に検索する

context['color'] -> 'blue'

context['number'] -> 2

context['food'] -> 'spam'

Context

number=42 food='spam'

animal='duck'

color='blue' number=2

dict

dict

dict

Page 14: Django boodoo

• 要するに、 {% with %} タグは、一時的にコンテキストに辞書を積んでブロックをレンダするタグ

• スコープの外の同名の変数は見えなくなる

• with のキーワード引数は、ブロックのレンダ直前に解決される

Page 15: Django boodoo

ということで・・・

Page 16: Django boodoo

変#1: デフォルト値

• コンテキストに username があるかどうかわからない

• username がなければ「名無し」さん

• username があればその値

{{ username }} これはダメ

{{ username|default:'名無し' }} 一応OK

<span>ようこそ{{ username|default:'名無し' }}さん</span> !... !<h1>{{ username|default:'名無し' }}さんのプロフィール</h1> !... !<h2>{{ username|default:'名無し' }}さんへのおすすめ</h2> !....

(#`Д́)ノノ┻┻;:'、・゙ヤッテラレルカ!

Page 17: Django boodoo

変#1: デフォルト値

{% with username=username|default:'名無し' %} !<span>ようこそ{{ username }}さん</span> !... !<h1>{{ username }}さんのプロフィール</h1> !... !<h2>{{ username }}さんへのおすすめ</h2> !.... !{% endwith %}

• コンテキスト変数を確実に埋めておきたいときは、{% with %}でデフォルト値をオーバライドしたブロックをつくる

• 同じ変数を何度も参照したいときにも、 {% with %} が有効

Page 18: Django boodoo

変#2: {% include %}の悪用

Hello {{ foo }}.

• {% include %} は、レンダ時にテンプレートを展開する(実際には、include 対象をレンダして結果を挿入する)

• レンダ対象にはコンテキストがそのまま渡る

{% with foo='bar' %} !{% include "included.html" %} !{% endwith %}

Hello bar.

Page 19: Django boodoo

変#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

Page 20: Django boodoo

変#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つで、フィールドをカスタマイズできる

Page 21: Django boodoo

Multi-DB

Page 22: Django boodoo

Django Multi-DB

• 1.2で導入された

• バックエンドDBのマスタスレーブ化、シャーディング、レプリケーションを可能にする

• モデル単位でDBのルーティングができる

Django App

Replicate/Partition

RO Access

RW Access

Page 23: Django boodoo

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',...]

Page 24: Django boodoo

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__

Page 25: Django boodoo

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

Page 26: Django boodoo

• と、いうことは、ConnectionHandlerの実装を動的に差し替えてやれば、ランタイムでDBの設定を切り替えられるはず

• DBの設定はPythonオブジェクト→Djangoのモデルにできるはず

Page 27: Django boodoo

やってみましたhttps://gist.github.com/whosaysni/11361218

Page 28: Django boodoo

Thanks!