revision-up-to: | 8961 (1.0) |
---|
Django にはオプションとして使える “sites” フレームワークが付属しています。 “sites” はオブジェクトや機能を特定の Web サイトに関連付けるためのフックであ ると同時に、 Django で作成したサイトのドメイン名と「分かりやすい名前 verbose name」を保存しています。
一つの Django で複数のサイトを管理していて、サイト間に違いを持たせたい場合 に使ってください。
sites フレームワークは、簡単なモデルだけでできています:
モデルには、 domain と name という二つのフィールドがあり ます。あるサイトの設定ファイルの SITE_ID 設定は、そのサイトを表 す Site オブジェクトのデータベース上 での ID を指定します。
site の使い道は自由ですが、 Django では簡単な呼び出し規約で自動的に sites を使える方法を 2 種類提供しています。
どういう状況で sites を使うのでしょうか?例を挙げて説明しましょう。
LJWorld.com と Lawrence.com は同じニュース組織、Kansaz 州 Lawrence にある Lawrence Journal-World newspaper が管理しています。 LJWorld.com はニュース に、 Lawrence.com は地域の娯楽情報にフォーカスしていますが、編集者は 両方の サイトで同じニュースを公開したいと考える場合もあります。
この問題を何も考えずに処理するなら、サイト構築担当者に対して、 LJWorld.com と Lawrence.com に同じ記事を出すよう 2 度依頼することになります。しかしこれ はサイト構築担当にとって非効率的ですし、同じ記事のコピーが複数データベース に入ることになってしまいます。
もっとましで簡単な方法ががあります: どちらのサイトも同じデータベースを使い、 1 つの記事を複数のサイトに関連づけるというものです。この関係は、 Django モ デルの用語で言えば Article モデルの ManyToManyField で表現されます:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
sites = models.ManyToManyField(Site)
このモデルは、以下のようにいくつかの点で優れています:
site 構築担当が 1 つのインタフェース (Django admin サイト) で両方のサ イトにある全てのコンテンツを編集できます。
同じ記事をデータベース上に何度も書き込まなくて済み、一つのレコードと して保存できます。
サイト開発者は同じビューコードを両方のサイトで使えます。ビューコード では、リクエストされている記事が現在のサイト用のものかチェックします。 例えば以下のようなコードになるでしょう:
from django.conf import settings
def article_detail(request, article_id):
try:
a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID)
except Article.DoesNotExist:
raise Http404
# ...
あるモデルを ForeignKey を使って 他対一の関係で Site に関連づけても構 いません。
例えば、ある記事をあるサイトだけで表示できるようにしたければ、モデルは以下 のように書きます:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
site = models.ForeignKey(Site)
この方法でも、前節で述べたのと同じような恩恵を得られます。
低水準では、 Django ビューの中で sites フレームワークを使って、ビューを呼び 出しているサイトごとに固有の処理を実行できます。
例えば:
from django.conf import settings def my_view(request): if settings.SITE_ID == 3: # Do something. else: # Do something else.
もちろん、上のようにサイト ID をハードコードするのは見栄えよくありません。 この種のハードコード化は、すぐに実行しなければならないハックのときにはベス トでしょう。同じことをよりクリーンに実現するには、サイトのドメイン名をチェッ クします:
from django.conf import settings from django.contrib.sites.models import Site def my_view(request): current_site = Site.objects.get(id=settings.SITE_ID) if current_site.domain == 'foo.com': # Do something else: # Do something else.
settings.SITE_ID の値から Site オブジェクトを取得するというイディ オムはよく使われるので、 Site のモデ ルマネジャには get_current() メソッドがあります。以下の例は上の例と同じ です:
from django.contrib.sites.models import Site def my_view(request): current_site = Site.objects.get_current() if current_site.domain == 'foo.com': # Do something else: # Do something else.
LJWorld.com と Lawrence.com には e-mail による通知機能があり、読者が登録し ておくとニュースが届いたときに通知メールを受け取れるようになっています。こ のからくりは簡単で、 Web フォームから登録すると、「ご登録ありがとうございま した」という内容のメールを受け取ります。
登録処理を行うコードを二度開発するのは非効率的で冗長なので、舞台裏では複数 サイトで同じコードを使うようにします。ただし、「ご登録ありがとうございます」 通知はサイトごとに変えなければなりません。 Site オブジェクトを使えば、現在のサイ トの name と domain の値を使って「ありがとうございます」メッセージ を抽象化できます。
フォーム処理ビューの例は以下の通りです:
from django.contrib.sites.models import Site
from django.core.mail import send_mail
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
current_site = Site.objects.get_current()
send_mail('Thanks for subscribing to %s alerts' % current_site.name,
'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name,
'editor@%s' % current_site.domain,
[user.email])
# ...
Lawrence.com では、 e-mail の件名は "Thanks for subscribing to lawrence.com alerts." です。 LJWorld.com では、件名は "Thanks for subscribing to LJWorld.com alerts." で、メール本体も同様です。
より柔軟 (で、重量な) 方法として、 Django テンプレートシステムを使った方法 と比較してみましょう。 Lawrence.com と LJWorld.com は別々のテンプレートディ レクトリ (TEMPLATE_DIRS) を持っているので、以下のようにすればサ イト間の違いをテンプレートシステムに追い出せます:
from django.core.mail import send_mail
from django.template import loader, Context
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template('alerts/subject.txt').render(Context({}))
message = loader.get_template('alerts/message.txt').render(Context({}))
send_mail(subject, message, 'editor@ljworld.com', [user.email])
# ...
この場合、 LJWorld.com と Lawrence.com のテンプレートディレクトリ下に subject.txt と message.txt の二つのテンプレートを作成します。 ただし、こうすればより柔軟にはなりますが、複雑さは増します。
不要な複雑さと冗長性を排除するためには、 Site オブジェクトを可能な限り使うとよ いでしょう。
Django の get_absolute_url() は、オブジェクトの URL をドメイン名なしで 取得する際には便利ですが、場合によっては、完全な URL、すなわち http:// とドメイン名、その他全てを表示したいこともあるでしょう。これには sites フレー ムワークを使います。簡単な例を示します:
>>> from django.contrib.sites.models import Site
>>> obj = MyModel.objects.get(id=3)
>>> obj.get_absolute_url()
'/mymodel/objects/3/'
>>> Site.objects.get_current().domain
'example.com'
>>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
'http://example.com/mymodel/objects/3/'
現在のサイトを表す Site オブジェクト は、もともとデータベースに保存されているので、 get_current() を呼ぶたび にデータベース呼び出しが発生してしまいます。ただし、 Django はもう少し賢く Site を扱います。
すなわち、現在のサイトをキャッシュし、それ以降 get_current() を呼び出し ても、データベースに触らずキャッシュから Site オブジェクトを返します。
何らかの理由で、データベースクエリを強制したい場合には、 clear_cache()`() を使って キャッシュを消去させられます:
# 最初の呼び出し: データベースから Site オブジェクトを取り出す
current_site = Site.objects.get_current()
# ...
# 2 回目の呼び出し: キャッシュから取り出す
current_site = Site.objects.get_current()
# ...
# 3 回目の呼び出しで、データベースクエリを強制する
Site.objects.clear_cache()
current_site = Site.objects.get_current()
アプリケーション内で Site が重要な働 きを持っている場合、 CurrentSiteManager という便利なクラ スを検討してみてください。
CurrentSiteManager はモデルの マネジャ クラスで、クエリ結果を現在の Site に関連づけられたオブジェクトだけ に自動的にフィルタします。
CurrentSiteManager を使うには、モデ ルの中に明示的に追加します。例を示します:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
site = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager()
このモデルでは、 Photo.objects.all() を使うとデーターベース上の全ての Photo オブジェクトを返しますが、 Photo.on_site.all() を使うと、 SITE_ID 設定に従って、現在のサイトに関連づけられた Photo オ ブジェクトだけを返します。
つまり、以下の二つの文は等価になります:
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
CurrentSiteManager は Photo の どのフィールドが Site なのかをどうやっ て見付けるのでしょう? CurrentSiteManager はデフォルトでは site という名前のフィールドを探します。モデル内で site 以外の 名前の ForeignKey や ManyToManyField` を定義している場 合、 CurrentSiteManager に明示的に フィールド名を渡す必要があります。以下のモデルでは、 publish_on という 名前のフィールドがその例です:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager('publish_on')
CurrentSiteManager を使って、存在し ないフィールド名を渡そうとすると、 Django は ValueError を送出します。
CurrentSiteManager を使っていたとし ても、結局は (サイト固有でない) 通常の Manager をモデル内に持っておく必 要に迫られるでしょう。 マネジャのドキュメント でも説明しましたが、手動でマネジャを定義すると、Django は objects = models.Manager() を使った自動マネジャ生成を行わなくなるからで す。また、 Django の一部 (例えば admin サイトや汎用ビュー) では、モデル内で 最初に定義されている マネジャを常に使うようになっています。そのため、 admin サイトから全てのオブジェクトにアクセスしたい (サイト固有のフィルタリ ングを行わない) 場合には、モデル内で objects = models.Manager() を CurrentSiteManager の定義より前 に配置しておかねばなりません。
Django を使う際、必ずしも sites フレームワークを使わねばならないというわけ ではありません。とはいえ、 Django はいくつかの場所で sites の恩恵を利用でき るので、ぜひとも sites を活用するよう勧めます。 Django を単一のサイトを駆動 するためだけに使っているとしても、ひと手間かけてサイトオブジェクトを作成し、 domain と name を指定して、その ID を SITE_ID 設定に指定 してみてください。
Django における sites フレームワークの役割は以下の通りです:
django.contrib 内のアプリケーションの中には、 sites フレームワークを利用はできるけれども、 必須にはしない ようなものが あります (sites を使いたくない人や、 sites フレームワークが必要とするデータ ベーステーブルの作成が 不可能な 人もいるためです)。こうした場合のために、 sites フレームワークでは RequestSite クラスを提供しています。このクラスは、データベースバックエンド上に sites フ レームワークがない場合のフォールバックとして使われます。
RequestSite オブジェクトは、通常の Site オブジェクトと同様のインタフェー スを備えていますが、 __init__() が HttpRequest オブジェクトを取るところが違います。この オブジェクトはリクエストのドメイン情報を見ることで domain や name を決定します。 Site オブジェクトとイ ンタフェースを合わせるため、 RequestSite オブジェクトにも save() や delete() といったメソッドが ありますが、これらのメソッドを呼び出すと NotImplementedError を送出 します。
Aug 31, 2012